Skip to content

Commit 1e64859

Browse files
Add swift crash producer support
When swift compiler crashed with swift caching enabled, create a crash reproducer automatically.
1 parent 446fc58 commit 1e64859

File tree

4 files changed

+67
-3
lines changed

4 files changed

+67
-3
lines changed

Sources/SwiftDriver/Driver/Driver.swift

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import struct TSCBasic.AbsolutePath
2323
import struct TSCBasic.ByteString
2424
import struct TSCBasic.Diagnostic
2525
import struct TSCBasic.FileInfo
26+
import struct TSCBasic.ProcessResult
2627
import struct TSCBasic.RelativePath
2728
import struct TSCBasic.SHA256
2829
import var TSCBasic.localFileSystem
@@ -71,6 +72,7 @@ public struct Driver {
7172
case conditionalCompilationFlagIsNotValidIdentifier(String)
7273
case baselineGenerationRequiresTopLevelModule(String)
7374
case optionRequiresAnother(String, String)
75+
case unableToCreateReproducer
7476
// Explicit Module Build Failures
7577
case malformedModuleDependency(String, String)
7678
case missingModuleDependency(String)
@@ -142,6 +144,8 @@ public struct Driver {
142144
return "generating a baseline with '\(arg)' is only supported with '-emit-module' or '-emit-module-path'"
143145
case .optionRequiresAnother(let first, let second):
144146
return "'\(first)' cannot be specified if '\(second)' is not present"
147+
case .unableToCreateReproducer:
148+
return "failed to create reproducer"
145149
}
146150
}
147151
}
@@ -962,7 +966,7 @@ public struct Driver {
962966
negative: .disableIncrementalFileHashing,
963967
default: false)
964968
self.recordedInputMetadata = .init(uniqueKeysWithValues:
965-
Set(inputFiles).compactMap { inputFile -> (TypedVirtualPath, FileMetadata)? in
969+
Set(inputFiles).compactMap { inputFile -> (TypedVirtualPath, FileMetadata)? in
966970
guard let modTime = try? fileSystem.lastModificationTime(for: inputFile.file) else { return nil }
967971
if incrementalFileHashes {
968972
guard let data = try? fileSystem.readFileContents(inputFile.file) else { return nil }
@@ -1957,7 +1961,8 @@ extension Driver {
19571961
buildRecordInfo: buildRecordInfo,
19581962
showJobLifecycle: showJobLifecycle,
19591963
argsResolver: executor.resolver,
1960-
diagnosticEngine: diagnosticEngine)
1964+
diagnosticEngine: diagnosticEngine,
1965+
reproducerCallback: self.generateReproducer)
19611966
}
19621967

19631968
private mutating func performTheBuild(
@@ -4019,3 +4024,36 @@ extension Driver {
40194024
return mapping
40204025
}
40214026
}
4027+
4028+
// Generate reproducer.
4029+
extension Driver {
4030+
func generateReproducer(_ job: Job, _ status: ProcessResult.ExitStatus) throws {
4031+
// If process is not terminated with certain error code, no need to create reproducer.
4032+
#if os(Windows)
4033+
guard case .abnormal = status else {
4034+
return;
4035+
}
4036+
#else
4037+
guard case .signalled = status else {
4038+
return;
4039+
}
4040+
#endif
4041+
guard isFrontendArgSupported(.genReproducer), enableCaching, job.kind.supportCaching else {
4042+
return
4043+
}
4044+
// TODO: check flag is supported.
4045+
try withTemporaryDirectory(dir: fileSystem.tempDirectory, prefix: "swift-reproducer", removeTreeOnDeinit: false) { tempDir in
4046+
var reproJob = job
4047+
reproJob.commandLine.appendFlag(.genReproducer)
4048+
reproJob.commandLine.appendFlag(.genReproducerDir)
4049+
reproJob.commandLine.appendPath(tempDir)
4050+
reproJob.outputs.removeAll()
4051+
reproJob.outputCacheKeys.removeAll()
4052+
let result = try executor.execute(job: reproJob, forceResponseFiles: false, recordedInputModificationDates: [:])
4053+
guard case .terminated(let code) = result.exitStatus, code == 0 else {
4054+
throw Error.unableToCreateReproducer
4055+
}
4056+
diagnosticEngine.emit(.note_reproducer_created(tempDir.pathString))
4057+
}
4058+
}
4059+
}

Sources/SwiftDriver/Driver/ToolExecutionDelegate.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import struct TSCBasic.Diagnostic
3030
import struct TSCBasic.ProcessResult
3131
import var TSCBasic.stderrStream
3232
import var TSCBasic.stdoutStream
33+
import class TSCBasic.Process
3334

3435
/// Delegate for printing execution information on the command-line.
3536
@_spi(Testing) public final class ToolExecutionDelegate: JobExecutionDelegate {
@@ -49,6 +50,8 @@ import var TSCBasic.stdoutStream
4950
case silent
5051
}
5152

53+
public typealias ReproducerCallback = (Job, ProcessResult.ExitStatus) throws -> Void
54+
5255
public let mode: Mode
5356
public let buildRecordInfo: BuildRecordInfo?
5457
public let showJobLifecycle: Bool
@@ -58,18 +61,21 @@ import var TSCBasic.stdoutStream
5861
private var nextBatchQuasiPID: Int
5962
private let argsResolver: ArgsResolver
6063
private var batchJobInputQuasiPIDMap = TwoLevelMap<Job, TypedVirtualPath, Int>()
64+
private let reproducerCallback: ReproducerCallback?
6165

6266
@_spi(Testing) public init(mode: ToolExecutionDelegate.Mode,
6367
buildRecordInfo: BuildRecordInfo?,
6468
showJobLifecycle: Bool,
6569
argsResolver: ArgsResolver,
66-
diagnosticEngine: DiagnosticsEngine) {
70+
diagnosticEngine: DiagnosticsEngine,
71+
reproducerCallback: ReproducerCallback? = nil) {
6772
self.mode = mode
6873
self.buildRecordInfo = buildRecordInfo
6974
self.showJobLifecycle = showJobLifecycle
7075
self.diagnosticEngine = diagnosticEngine
7176
self.argsResolver = argsResolver
7277
self.nextBatchQuasiPID = ToolExecutionDelegate.QUASI_PID_START
78+
self.reproducerCallback = reproducerCallback
7379
}
7480

7581
public func jobStarted(job: Job, arguments: [String], pid: Int) {
@@ -107,6 +113,14 @@ import var TSCBasic.stdoutStream
107113
}
108114
#endif
109115

116+
if let reproducerCallback = reproducerCallback {
117+
do {
118+
try reproducerCallback(job, result.exitStatus)
119+
} catch {
120+
diagnosticEngine.emit(.error_failed_to_create_reproducer)
121+
}
122+
}
123+
110124
switch mode {
111125
case .silent:
112126
break

Sources/SwiftDriver/Utilities/Diagnostics.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,12 @@ extension Diagnostic.Message {
182182
static var error_no_objc_interop_embedded: Diagnostic.Message {
183183
.error("Objective-C interop cannot be enabled with embedded Swift.")
184184
}
185+
186+
static var error_failed_to_create_reproducer: Diagnostic.Message {
187+
.error("failed to create crash reproducer")
188+
}
189+
190+
static func note_reproducer_created(_ path: String) -> Diagnostic.Message {
191+
.note("crash reproducer is created at: \(path)")
192+
}
185193
}

Sources/SwiftOptions/Options.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,8 @@ extension Option {
586586
public static let F: Option = Option("-F", .joinedOrSeparate, attributes: [.frontend, .synthesizeInterface, .argumentIsPath], helpText: "Add directory to framework search path")
587587
public static let gccToolchain: Option = Option("-gcc-toolchain", .separate, attributes: [.helpHidden, .argumentIsPath], metaVar: "<path>", helpText: "Specify a directory where the clang importer and clang linker can find headers and libraries")
588588
public static let gdwarfTypes: Option = Option("-gdwarf-types", .flag, attributes: [.frontend], helpText: "Emit full DWARF type info.", group: .g)
589+
public static let genReproducerDir: Option = Option("-gen-reproducer-dir", .separate, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Path to directory where reproducers write to.")
590+
public static let genReproducer: Option = Option("-gen-reproducer", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Generate a reproducer for current compilation.")
589591
public static let generateEmptyBaseline: Option = Option("-generate-empty-baseline", .flag, attributes: [.noDriver], helpText: "Generate an empty baseline")
590592
public static let generateEmptyBaseline_: Option = Option("--generate-empty-baseline", .flag, alias: Option.generateEmptyBaseline, attributes: [.noDriver], helpText: "Generate an empty baseline")
591593
public static let generateMigrationScript: Option = Option("-generate-migration-script", .flag, attributes: [.noDriver], helpText: "Compare SDK content in JSON file and generate migration script")
@@ -1553,6 +1555,8 @@ extension Option {
15531555
Option.F,
15541556
Option.gccToolchain,
15551557
Option.gdwarfTypes,
1558+
Option.genReproducerDir,
1559+
Option.genReproducer,
15561560
Option.generateEmptyBaseline,
15571561
Option.generateEmptyBaseline_,
15581562
Option.generateMigrationScript,

0 commit comments

Comments
 (0)