Skip to content

Commit

Permalink
[driver] Added support for detecting and reporting crashed subprocesses.
Browse files Browse the repository at this point in the history
Added a TaskSignalledCallback to TaskQueue, which will be called instead of
TaskFinishedCallback if the task exited abnormally.

In Unix/TaskQueue.inc, check WIFSIGNALED if the task did not pass WIFEXITED,
and call the TaskSignalledCallback if necessary. In Default/TaskQueue.inc, check
for a return code of -2; if present, call the TaskSignalledCallback instead of
the TaskFinishedCallback.

Updated Compilation to pass a TaskSignalledCallback.

Added diagnostics to indicate when a command signalled as well as when a command
failed with either poor diagnostics or a non-1 exit code. (These match Clang’s
diagnostics.) Added tests to ensure these diagnostics are emitted when the
frontend crashes or fails an assertion (if assertions are available).

This fixes <rdar://problem/16012199>.

Swift SVN r13654
  • Loading branch information
cwakamo committed Feb 7, 2014
1 parent 9747d87 commit b7d4e98
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 19 deletions.
8 changes: 8 additions & 0 deletions include/swift/AST/DiagnosticsDriver.def
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ WARNING(warning_parallel_execution_not_supported,driver,none,
"parallel execution not supported; falling back to serial execution",
())

ERROR(error_unable_to_execute_command,driver,none,
"unable to execute command: %0", (StringRef))
ERROR(error_command_signalled,driver,none,
"%0 command failed due to signal (use -v to see invocation)", (StringRef))
ERROR(error_command_failed,driver,none,
"%0 command failed with exit code %1 (use -v to see invocation)",
(StringRef, int))

ERROR(error_cannot_specify__o_for_multiple_outputs,driver,none,
"cannot specify -o when generating multiple output files", ())

Expand Down
30 changes: 26 additions & 4 deletions include/swift/Basic/TaskQueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,22 @@ class TaskQueue {
typedef std::function<TaskFinishedResponse(ProcessId Pid, int ReturnCode,
StringRef Output, void *Context)>
TaskFinishedCallback;

/// \brief A callback which will be executed if a task exited abnormally due
/// to a signal.
///
/// \param Pid the ProcessId of the task which exited abnormally.
/// \param ErrorMsg a string describing why the task exited abnormally. If
/// no reason could be deduced, this may be empty.
/// \param Output the output from the task which exited abnormally, if
/// available. (This may not be available on all platforms.)
/// \param Context the context which was passed when the task was added
///
/// \returns a TaskFinishedResponse indicating whether or not execution
/// should proceed
typedef std::function<TaskFinishedResponse(ProcessId Pid, StringRef ErrorMsg,
StringRef Output, void *Context)>
TaskSignalledCallback;
#pragma clang diagnostic pop

/// \brief Indicates whether TaskQueue supports buffering output on the
Expand Down Expand Up @@ -112,10 +128,14 @@ class TaskQueue {
///
/// \param Began a callback which will be called when a task begins
/// \param Finished a callback which will be called when a task finishes
/// \param Signalled a callback which will be called if a task exited
/// abnormally due to a signal
///
/// \returns true if all tasks did not execute successfully
virtual bool execute(TaskBeganCallback Began = TaskBeganCallback(),
TaskFinishedCallback Finished = TaskFinishedCallback());
virtual bool
execute(TaskBeganCallback Began = TaskBeganCallback(),
TaskFinishedCallback Finished = TaskFinishedCallback(),
TaskSignalledCallback Signalled = TaskSignalledCallback());
};

/// \brief A class which simulates execution of tasks with behavior similar to
Expand Down Expand Up @@ -144,8 +164,10 @@ class DummyTaskQueue : public TaskQueue {
ArrayRef<const char *> Env = llvm::None,
void *Context = nullptr);

virtual bool execute(TaskBeganCallback Began = TaskBeganCallback(),
TaskFinishedCallback Finished = TaskFinishedCallback());
virtual bool
execute(TaskBeganCallback Began = TaskBeganCallback(),
TaskFinishedCallback Finished = TaskFinishedCallback(),
TaskSignalledCallback Signalled = TaskSignalledCallback());
};

} // end namespace sys
Expand Down
6 changes: 4 additions & 2 deletions include/swift/Driver/Compilation.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ class Compilation {
}

/// Asks the Compilation to perform the Jobs which it knows about.
/// \returns result code for the Compilation's Jobs; 0 indicates success
/// \returns result code for the Compilation's Jobs; 0 indicates success and
/// -2 indicates that one of the Compilation's Jobs crashed during execution
int performJobs();

private:
Expand All @@ -118,7 +119,8 @@ class Compilation {
/// \param FinishedCommands a set of Commands which have finished execution,
/// or which are known not to need to execute.
///
/// \returns exit code of the first failed Job, or 0 on success
/// \returns exit code of the first failed Job, or 0 on success. A return
/// value of -2 indicates that a Job crashed during execution.
int performJobsInList(const JobList &JL,
llvm::DenseSet<const Command *> &ScheduledCommands,
llvm::DenseSet<const Command *> &FinishedCommands);
Expand Down
34 changes: 26 additions & 8 deletions lib/Basic/Default/TaskQueue.inc
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ void TaskQueue::addTask(const char *ExecPath, ArrayRef<const char *> Args,
QueuedTasks.push(std::move(T));
}

bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished){
bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
TaskSignalledCallback Signalled) {
bool ContinueExecution = true;

// This implementation of TaskQueue doesn't support parallel execution.
Expand Down Expand Up @@ -104,13 +105,30 @@ bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished){
Began(PI.Pid, T->Context);
}

PI = Wait(PI, 0, true);
if (Finished) {
TaskFinishedResponse Response = Finished(PI.Pid, PI.ReturnCode,
StringRef(), T->Context);
ContinueExecution = Response != TaskFinishedResponse::StopExecution;
} else if (PI.ReturnCode != 0) {
ContinueExecution = false;
std::string ErrMsg;
PI = Wait(PI, 0, true, &ErrMsg);
int ReturnCode = PI.ReturnCode;
if (ReturnCode == -2) {
// Wait() returning a return code of -2 indicates the process received
// a signal during execution.
if (Signalled) {
TaskFinishedResponse Response = Signalled(PI.Pid, ErrMsg, StringRef(),
T->Context);
ContinueExecution = Response != TaskFinishedResponse::StopExecution;
} else {
// If we don't have a Signalled callback, unconditionally stop.
ContinueExecution = false;
}
} else {
// Wait() returned a normal return code, so just indicate that the task
// finished.
if (Finished) {
TaskFinishedResponse Response = Finished(PI.Pid, PI.ReturnCode,
StringRef(), T->Context);
ContinueExecution = Response != TaskFinishedResponse::StopExecution;
} else if (PI.ReturnCode != 0) {
ContinueExecution = false;
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion lib/Basic/TaskQueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ void DummyTaskQueue::addTask(const char *ExecPath, ArrayRef<const char *> Args,
}

bool DummyTaskQueue::execute(TaskQueue::TaskBeganCallback Began,
TaskQueue::TaskFinishedCallback Finished) {
TaskQueue::TaskFinishedCallback Finished,
TaskQueue::TaskSignalledCallback Signalled) {
typedef std::pair<pid_t, std::unique_ptr<DummyTask>> PidTaskPair;
std::queue<PidTaskPair> ExecutingTasks;

Expand Down
23 changes: 21 additions & 2 deletions lib/Basic/Unix/TaskQueue.inc
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,8 @@ void TaskQueue::addTask(const char *ExecPath, ArrayRef<const char *> Args,
QueuedTasks.push(std::move(T));
}

bool TaskQueue::execute(TaskBeganCallback Began,
TaskFinishedCallback Finished) {
bool TaskQueue::execute(TaskBeganCallback Began, TaskFinishedCallback Finished,
TaskSignalledCallback Signalled) {
typedef llvm::DenseMap<pid_t, std::unique_ptr<Task>> PidToTaskMap;

// Stores the current executing Tasks, organized by pid.
Expand Down Expand Up @@ -340,6 +340,25 @@ bool TaskQueue::execute(TaskBeganCallback Began,
// which returned a nonzero exit code as having failed.
SubtaskFailed = true;
}
} else if (WIFSIGNALED(Status)) {
// The process exited due to a signal.
int Signal = WTERMSIG(Status);

StringRef ErrorMsg = strsignal(Signal);

if (Signalled) {
TaskFinishedResponse Response = Signalled(T.getPid(), ErrorMsg,
T.getOutput(),
T.getContext());
if (Response == TaskFinishedResponse::StopExecution)
// If we have a TaskCrashedCallback, only set SubtaskFailed to
// true if the callback returns StopExecution.
SubtaskFailed = true;
} else {
// Since we don't have a TaskCrashedCallback, treat a crashing
// subtask as having failed.
SubtaskFailed = true;
}
}

ExecutingTasks.erase(Pid);
Expand Down
32 changes: 30 additions & 2 deletions lib/Driver/Compilation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "swift/Basic/TaskQueue.h"
#include "swift/Driver/Driver.h"
#include "swift/Driver/Job.h"
#include "swift/Driver/Tool.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Support/raw_ostream.h"
Expand Down Expand Up @@ -145,6 +146,8 @@ int Compilation::performJobsInList(const JobList &JL,
if (TaskQueue::supportsBufferingOutput())
llvm::errs() << Output;

const Command *FinishedCmd = (const Command *)Context;

if (ReturnCode != 0) {
// The task failed, so return true without performing any further
// dependency analysis.
Expand All @@ -154,14 +157,18 @@ int Compilation::performJobsInList(const JobList &JL,
if (Result == 0)
Result = ReturnCode;

if (!FinishedCmd->getCreator().hasGoodDiagnostics() || ReturnCode != 1)
Diags.diagnose(SourceLoc(), diag::error_command_failed,
FinishedCmd->getCreator().getNameForDiagnostics(),
ReturnCode);

return TaskFinishedResponse::StopExecution;
}

// When a task finishes, we need to reevaluate the other Commands in our
// JobList.

// TODO: use the Command which just finished to evaluate other Commands.
const Command *FinishedCmd = (const Command *)Context;
FinishedCommands.insert(FinishedCmd);
for (const Job *J : JL) {
if (const Command *Cmd = dyn_cast<Command>(J)) {
Expand All @@ -181,8 +188,29 @@ int Compilation::performJobsInList(const JobList &JL,
return TaskFinishedResponse::ContinueExecution;
};

auto taskSignalled = [&] (ProcessId Pid, StringRef ErrorMsg, StringRef Output,
void *Context) -> TaskFinishedResponse {
// First, send the buffered output to stderr, though only if we support
// getting buffered output.
if (TaskQueue::supportsBufferingOutput())
llvm::errs() << Output;

if (!ErrorMsg.empty())
Diags.diagnose(SourceLoc(), diag::error_unable_to_execute_command,
ErrorMsg);

const Command *FailedCmd = (const Command *)Context;
Diags.diagnose(SourceLoc(), diag::error_command_signalled,
FailedCmd->getCreator().getNameForDiagnostics());

// Since the task signalled, so unconditionally set result to -2.
Result = -2;

return TaskFinishedResponse::StopExecution;
};

// Ask the TaskQueue to execute.
TQ->execute(taskBegan, taskFinished);
TQ->execute(taskBegan, taskFinished, taskSignalled);

return Result;
}
Expand Down
8 changes: 8 additions & 0 deletions test/Driver/assert.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// RUN: not %swift_driver -emit-executable -o %t.exe %s -Xfrontend -debug-assert-immediately 2>&1 | FileCheck %s
// RUN: not %swift_driver -emit-executable -o %t.exe %s -Xfrontend -debug-assert-after-parse 2>&1 | FileCheck %s

// REQUIRES: asserts

// CHECK: error: swift frontend command failed due to signal

println("Hello, World")
7 changes: 7 additions & 0 deletions test/Driver/crash.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// RUN: not %swift_driver -emit-executable -o %t.exe %s -Xfrontend -debug-crash-immediately 2>&1 | FileCheck %s

// RUN: not %swift_driver -emit-executable -o %t.exe %s -Xfrontend -debug-crash-after-parse 2>&1 | FileCheck %s

// CHECK: error: swift frontend command failed due to signal

println("Hello, World")

0 comments on commit b7d4e98

Please sign in to comment.