Skip to content

Commit aeff0a4

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

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
@@ -69,6 +70,7 @@ public struct Driver {
6970
case conditionalCompilationFlagIsNotValidIdentifier(String)
7071
case baselineGenerationRequiresTopLevelModule(String)
7172
case optionRequiresAnother(String, String)
73+
case unableToCreateReproducer
7274
// Explicit Module Build Failures
7375
case malformedModuleDependency(String, String)
7476
case missingModuleDependency(String)
@@ -140,6 +142,8 @@ public struct Driver {
140142
return "generating a baseline with '\(arg)' is only supported with '-emit-module' or '-emit-module-path'"
141143
case .optionRequiresAnother(let first, let second):
142144
return "'\(first)' cannot be specified if '\(second)' is not present"
145+
case .unableToCreateReproducer:
146+
return "failed to create reproducer"
143147
}
144148
}
145149
}
@@ -955,7 +959,7 @@ public struct Driver {
955959
negative: .disableIncrementalFileHashing,
956960
default: false)
957961
self.recordedInputMetadata = .init(uniqueKeysWithValues:
958-
Set(inputFiles).compactMap { inputFile -> (TypedVirtualPath, FileMetadata)? in
962+
Set(inputFiles).compactMap { inputFile -> (TypedVirtualPath, FileMetadata)? in
959963
guard let modTime = try? fileSystem.lastModificationTime(for: inputFile.file) else { return nil }
960964
if incrementalFileHashes {
961965
guard let data = try? fileSystem.readFileContents(inputFile.file) else { return nil }
@@ -1950,7 +1954,8 @@ extension Driver {
19501954
buildRecordInfo: buildRecordInfo,
19511955
showJobLifecycle: showJobLifecycle,
19521956
argsResolver: executor.resolver,
1953-
diagnosticEngine: diagnosticEngine)
1957+
diagnosticEngine: diagnosticEngine,
1958+
reproducerCallback: self.generateReproducer)
19541959
}
19551960

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

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
@@ -585,6 +585,8 @@ extension Option {
585585
public static let F: Option = Option("-F", .joinedOrSeparate, attributes: [.frontend, .synthesizeInterface, .argumentIsPath], helpText: "Add directory to framework search path")
586586
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")
587587
public static let gdwarfTypes: Option = Option("-gdwarf-types", .flag, attributes: [.frontend], helpText: "Emit full DWARF type info.", group: .g)
588+
public static let genReproducerDir: Option = Option("-gen-reproducer-dir", .separate, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Path to directory where reproducers write to.")
589+
public static let genReproducer: Option = Option("-gen-reproducer", .flag, attributes: [.helpHidden, .frontend, .noDriver], helpText: "Generate a reproducer for current compilation.")
588590
public static let generateEmptyBaseline: Option = Option("-generate-empty-baseline", .flag, attributes: [.noDriver], helpText: "Generate an empty baseline")
589591
public static let generateEmptyBaseline_: Option = Option("--generate-empty-baseline", .flag, alias: Option.generateEmptyBaseline, attributes: [.noDriver], helpText: "Generate an empty baseline")
590592
public static let generateMigrationScript: Option = Option("-generate-migration-script", .flag, attributes: [.noDriver], helpText: "Compare SDK content in JSON file and generate migration script")
@@ -1551,6 +1553,8 @@ extension Option {
15511553
Option.F,
15521554
Option.gccToolchain,
15531555
Option.gdwarfTypes,
1556+
Option.genReproducerDir,
1557+
Option.genReproducer,
15541558
Option.generateEmptyBaseline,
15551559
Option.generateEmptyBaseline_,
15561560
Option.generateMigrationScript,

0 commit comments

Comments
 (0)