diff --git a/Sources/SwiftDriver/Driver/Driver.swift b/Sources/SwiftDriver/Driver/Driver.swift index 291650956..9e57cb127 100644 --- a/Sources/SwiftDriver/Driver/Driver.swift +++ b/Sources/SwiftDriver/Driver/Driver.swift @@ -23,6 +23,7 @@ import struct TSCBasic.AbsolutePath import struct TSCBasic.ByteString import struct TSCBasic.Diagnostic import struct TSCBasic.FileInfo +import struct TSCBasic.ProcessResult import struct TSCBasic.RelativePath import var TSCBasic.localFileSystem import var TSCBasic.stderrStream @@ -68,6 +69,7 @@ public struct Driver { case conditionalCompilationFlagIsNotValidIdentifier(String) case baselineGenerationRequiresTopLevelModule(String) case optionRequiresAnother(String, String) + case unableToCreateReproducer // Explicit Module Build Failures case malformedModuleDependency(String, String) case missingModuleDependency(String) @@ -137,6 +139,8 @@ public struct Driver { return "generating a baseline with '\(arg)' is only supported with '-emit-module' or '-emit-module-path'" case .optionRequiresAnother(let first, let second): return "'\(first)' cannot be specified if '\(second)' is not present" + case .unableToCreateReproducer: + return "failed to create reproducer" } } } @@ -1978,7 +1982,8 @@ extension Driver { buildRecordInfo: buildRecordInfo, showJobLifecycle: showJobLifecycle, argsResolver: executor.resolver, - diagnosticEngine: diagnosticEngine) + diagnosticEngine: diagnosticEngine, + reproducerCallback: self.generateReproducer) } private mutating func performTheBuild( @@ -4044,3 +4049,34 @@ extension Driver { return mapping } } + +// Generate reproducer. +extension Driver { + func generateReproducer(_ job: Job, _ status: ProcessResult.ExitStatus) throws { + // If process is not terminated with certain error code, no need to create reproducer. +#if os(Windows) + guard case .abnormal = status else { + return; + } +#else + guard case .signalled = status else { + return; + } +#endif + // TODO: check flag is supported. + try withTemporaryDirectory(dir: fileSystem.tempDirectory, prefix: "swift-reproducer", removeTreeOnDeinit: false) { tempDir in + var reproJob = job + reproJob.kind = .reproducer + reproJob.commandLine.appendFlag("-gen-reproducer") + reproJob.commandLine.appendFlag("-gen-reproducer-dir") + reproJob.commandLine.appendPath(tempDir) + reproJob.outputs.removeAll() + reproJob.outputCacheKeys.removeAll() + let result = try executor.execute(job: reproJob, forceResponseFiles: false, recordedInputModificationDates: [:]) + guard case .terminated(let code) = result.exitStatus, code == 0 else { + throw Error.unableToCreateReproducer + } + diagnosticEngine.emit(.note_reproducer_created(tempDir.pathString)) + } + } +} diff --git a/Sources/SwiftDriver/Driver/ToolExecutionDelegate.swift b/Sources/SwiftDriver/Driver/ToolExecutionDelegate.swift index 4b409aca6..13484a35c 100644 --- a/Sources/SwiftDriver/Driver/ToolExecutionDelegate.swift +++ b/Sources/SwiftDriver/Driver/ToolExecutionDelegate.swift @@ -30,6 +30,7 @@ import struct TSCBasic.Diagnostic import struct TSCBasic.ProcessResult import var TSCBasic.stderrStream import var TSCBasic.stdoutStream +import class TSCBasic.Process /// Delegate for printing execution information on the command-line. @_spi(Testing) public final class ToolExecutionDelegate: JobExecutionDelegate { @@ -49,6 +50,8 @@ import var TSCBasic.stdoutStream case silent } + public typealias ReproducerCallback = (Job, ProcessResult.ExitStatus) throws -> Void + public let mode: Mode public let buildRecordInfo: BuildRecordInfo? public let showJobLifecycle: Bool @@ -58,18 +61,21 @@ import var TSCBasic.stdoutStream private var nextBatchQuasiPID: Int private let argsResolver: ArgsResolver private var batchJobInputQuasiPIDMap = TwoLevelMap() + private let reproducerCallback: ReproducerCallback? @_spi(Testing) public init(mode: ToolExecutionDelegate.Mode, buildRecordInfo: BuildRecordInfo?, showJobLifecycle: Bool, argsResolver: ArgsResolver, - diagnosticEngine: DiagnosticsEngine) { + diagnosticEngine: DiagnosticsEngine, + reproducerCallback: ReproducerCallback? = nil) { self.mode = mode self.buildRecordInfo = buildRecordInfo self.showJobLifecycle = showJobLifecycle self.diagnosticEngine = diagnosticEngine self.argsResolver = argsResolver self.nextBatchQuasiPID = ToolExecutionDelegate.QUASI_PID_START + self.reproducerCallback = reproducerCallback } public func jobStarted(job: Job, arguments: [String], pid: Int) { @@ -107,6 +113,14 @@ import var TSCBasic.stdoutStream } #endif + if let reproducerCallback = reproducerCallback { + do { + try reproducerCallback(job, result.exitStatus) + } catch { + diagnosticEngine.emit(.error_failed_to_create_reproducer) + } + } + switch mode { case .silent: break diff --git a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift index c406f5241..d7d8924f1 100644 --- a/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift +++ b/Sources/SwiftDriver/Jobs/FrontendJobHelpers.swift @@ -104,7 +104,7 @@ extension Driver { .help, .link, .verifyDebugInfo, .scanDependencies, .emitSupportedFeatures, .moduleWrap, .generateAPIBaseline, .generateABIBaseline, .compareAPIBaseline, - .compareABIBaseline, .printSupportedFeatures: + .compareABIBaseline, .printSupportedFeatures, .reproducer: jobNeedPathRemap = false } } else { diff --git a/Sources/SwiftDriver/Jobs/Job.swift b/Sources/SwiftDriver/Jobs/Job.swift index 2b2f5c370..8731214e9 100644 --- a/Sources/SwiftDriver/Jobs/Job.swift +++ b/Sources/SwiftDriver/Jobs/Job.swift @@ -25,6 +25,7 @@ public struct Job: Codable, Equatable, Hashable { case emitModule = "emit-module" case generatePCH = "generate-pch" case moduleWrap = "module-wrap" + case reproducer = "gen-reproducer" /// Generate a compiled Clang module. case generatePCM = "generate-pcm" @@ -255,6 +256,9 @@ extension Job : CustomStringConvertible { case .printSupportedFeatures: return "Print supported upcoming and experimental features" + + case .reproducer: + return "Generate reproducer for the crash" } } @@ -273,7 +277,7 @@ extension Job.Kind { public var isSwiftFrontend: Bool { switch self { case .backend, .compile, .mergeModule, .emitModule, .compileModuleFromInterface, .generatePCH, - .generatePCM, .dumpPCM, .interpret, .repl, .printTargetInfo, + .generatePCM, .dumpPCM, .interpret, .repl, .printTargetInfo, .reproducer, .versionRequest, .emitSupportedFeatures, .scanDependencies, .verifyModuleInterface, .printSupportedFeatures: return true @@ -294,7 +298,7 @@ extension Job.Kind { .help, .link, .verifyDebugInfo, .scanDependencies, .emitSupportedFeatures, .moduleWrap, .verifyModuleInterface, .generateAPIBaseline, .generateABIBaseline, .compareAPIBaseline, - .compareABIBaseline, .printSupportedFeatures: + .compareABIBaseline, .printSupportedFeatures, .reproducer: return false } } @@ -309,7 +313,7 @@ extension Job.Kind { .versionRequest, .autolinkExtract, .generateDSYM, .help, .link, .verifyDebugInfo, .scanDependencies, .emitSupportedFeatures, .moduleWrap, .generateAPIBaseline, .generateABIBaseline, .compareAPIBaseline, - .compareABIBaseline, .printSupportedFeatures: + .compareABIBaseline, .printSupportedFeatures, .reproducer: return false } } diff --git a/Sources/SwiftDriver/Utilities/Diagnostics.swift b/Sources/SwiftDriver/Utilities/Diagnostics.swift index 8b41e2d05..6bf53eed0 100644 --- a/Sources/SwiftDriver/Utilities/Diagnostics.swift +++ b/Sources/SwiftDriver/Utilities/Diagnostics.swift @@ -182,4 +182,12 @@ extension Diagnostic.Message { static var error_no_objc_interop_embedded: Diagnostic.Message { .error("Objective-C interop cannot be enabled with embedded Swift.") } + + static var error_failed_to_create_reproducer: Diagnostic.Message { + .error("failed to create crash reproducer") + } + + static func note_reproducer_created(_ path: String) -> Diagnostic.Message { + .note("crash reproducer is created at: \(path)") + } }