Skip to content

Commit e8a1d93

Browse files
xinhaoyuancopybara-github
authored andcommitted
Expose WriteFailureDescription instead of passing it to OnFailure. #Centipede
This enables recording failures before RunnerMain (where OnFailure would be called), and simplifies the ownership/lifetime reasoning for OnFailure handler. PiperOrigin-RevId: 753705766
1 parent 1165a5b commit e8a1d93

File tree

3 files changed

+37
-62
lines changed

3 files changed

+37
-62
lines changed

centipede/runner.cc

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -100,31 +100,6 @@ GlobalRunnerState state __attribute__((init_priority(200)));
100100
// This avoids calls to __tls_init() in hot functions that use `tls`.
101101
__thread ThreadLocalRunnerState tls;
102102

103-
// Tries to write `description` to `state.failure_description_path`.
104-
static void WriteFailureDescription(const char *description) {
105-
// TODO(b/264715830): Remove I/O error logging once the bug is fixed?
106-
if (state.failure_description_path == nullptr) return;
107-
// Make sure that the write is atomic and only happens once.
108-
[[maybe_unused]] static int write_once = [=] {
109-
FILE *f = fopen(state.failure_description_path, "w");
110-
if (f == nullptr) {
111-
perror("FAILURE: fopen()");
112-
return 0;
113-
}
114-
const auto len = strlen(description);
115-
if (fwrite(description, 1, len, f) != len) {
116-
perror("FAILURE: fwrite()");
117-
}
118-
if (fflush(f) != 0) {
119-
perror("FAILURE: fflush()");
120-
}
121-
if (fclose(f) != 0) {
122-
perror("FAILURE: fclose()");
123-
}
124-
return 0;
125-
}();
126-
}
127-
128103
void ThreadLocalRunnerState::TraceMemCmp(uintptr_t caller_pc, const uint8_t *s1,
129104
const uint8_t *s2, size_t n,
130105
bool is_equal) {
@@ -279,7 +254,7 @@ static void CheckWatchdogLimits() {
279254
"https://github.com/google/fuzztest/tree/main/doc/flags-reference.md"
280255
"\n",
281256
resource.what, resource.limit, resource.units, resource.value);
282-
WriteFailureDescription(resource.failure);
257+
CentipedeSetFailureDescription(resource.failure);
283258
std::abort();
284259
}
285260
}
@@ -315,7 +290,7 @@ __attribute__((noinline)) void CheckStackLimit(uintptr_t sp) {
315290
" > %zu"
316291
" (byte); aborting\n",
317292
tls.top_frame_sp - sp, stack_limit);
318-
fuzztest::internal::WriteFailureDescription(
293+
CentipedeSetFailureDescription(
319294
fuzztest::internal::kExecutionFailureStackLimitExceeded.data());
320295
std::abort();
321296
}
@@ -595,9 +570,6 @@ bool RunnerCallbacks::Mutate(
595570
return true;
596571
}
597572

598-
void RunnerCallbacks::OnFailure(
599-
std::function<void(std::string_view)> /*failure_description_callback*/) {}
600-
601573
class LegacyRunnerCallbacks : public RunnerCallbacks {
602574
public:
603575
LegacyRunnerCallbacks(FuzzerTestOneInputCallback test_one_input_cb,
@@ -1171,10 +1143,6 @@ int RunnerMain(int argc, char **argv, RunnerCallbacks &callbacks) {
11711143
return EXIT_SUCCESS;
11721144
}
11731145

1174-
callbacks.OnFailure([](std::string_view failure_description) {
1175-
WriteFailureDescription(std::string(failure_description).c_str());
1176-
});
1177-
11781146
// Inputs / outputs from shmem.
11791147
if (state.HasFlag(":shmem:")) {
11801148
if (!state.arg1 || !state.arg2) return EXIT_FAILURE;
@@ -1319,3 +1287,27 @@ extern "C" void CentipedeSetExecutionResult(const uint8_t *data, size_t size) {
13191287
state.execution_result_override->num_outputs_read() == 1,
13201288
"Failed to set execution result from CentipedeSetExecutionResult");
13211289
}
1290+
1291+
extern "C" void CentipedeSetFailureDescription(const char *description) {
1292+
using fuzztest::internal::state;
1293+
if (state.failure_description_path == nullptr) return;
1294+
// Make sure that the write is atomic and only happens once.
1295+
[[maybe_unused]] static int write_once = [=] {
1296+
FILE *f = fopen(state.failure_description_path, "w");
1297+
if (f == nullptr) {
1298+
perror("FAILURE: fopen()");
1299+
return 0;
1300+
}
1301+
const auto len = strlen(description);
1302+
if (fwrite(description, 1, len, f) != len) {
1303+
perror("FAILURE: fwrite()");
1304+
}
1305+
if (fflush(f) != 0) {
1306+
perror("FAILURE: fflush()");
1307+
}
1308+
if (fclose(f) != 0) {
1309+
perror("FAILURE: fclose()");
1310+
}
1311+
return 0;
1312+
}();
1313+
}

centipede/runner_interface.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ extern "C" size_t CentipedeGetCoverageData(uint8_t *data, size_t capacity);
126126
// "empty" with no features or metadata.
127127
extern "C" void CentipedeSetExecutionResult(const uint8_t *data, size_t size);
128128

129+
// Set the failure description for the runner to propagate further. Only the
130+
// description from the first call will be used.
131+
extern "C" void CentipedeSetFailureDescription(const char *description);
132+
129133
namespace fuzztest::internal {
130134

131135
// Callbacks interface implemented by the fuzzer and called by the runner.
@@ -153,11 +157,6 @@ class RunnerCallbacks {
153157
virtual bool Mutate(const std::vector<MutationInputRef> &inputs,
154158
size_t num_mutants,
155159
std::function<void(ByteSpan)> new_mutant_callback);
156-
// Registers a function to be called when a failure happens. If the
157-
// implementation supports this functionality, it will call the function with
158-
// a description of the failure. Otherwise, it will do nothing.
159-
virtual void OnFailure(
160-
std::function<void(std::string_view)> failure_description_callback);
161160
virtual ~RunnerCallbacks() = default;
162161
};
163162

fuzztest/internal/centipede_adaptor.cc

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -453,29 +453,6 @@ class CentipedeAdaptorRunnerCallbacks
453453
return configuration_.Serialize();
454454
}
455455

456-
void OnFailure(std::function<void(std::string_view)>
457-
failure_description_callback) override {
458-
// We register the callback only once. This is because `runtime_` is a
459-
// global singleton object, and hence previously registered callbacks remain
460-
// in the registry. In normal circumstances, there should be only one
461-
// runner callback object and a single call to this method, but there are
462-
// corner cases when multiple runner callback objects are created, e.g.,
463-
// when Centipede runs multiple fuzz tests in the multi-process mode.
464-
[[maybe_unused]] static bool callback_registered =
465-
[this, failure_description_callback =
466-
std::move(failure_description_callback)]() mutable {
467-
runtime_.RegisterCrashMetadataListener(
468-
[failure_description_callback =
469-
std::move(failure_description_callback)](
470-
absl::string_view crash_type,
471-
absl::Span<const std::string> /*stack_frames*/) {
472-
failure_description_callback(
473-
{crash_type.data(), crash_type.size()});
474-
});
475-
return true;
476-
}();
477-
}
478-
479456
bool HasCustomMutator() const override { return true; }
480457

481458
bool Mutate(const std::vector<fuzztest::internal::MutationInputRef>& inputs,
@@ -739,6 +716,13 @@ bool CentipedeFuzzerAdaptor::Run(int* argc, char*** argv, RunMode mode,
739716
// enabled in the controller mode to handle test setup failures.
740717
runtime_.EnableReporter(&fuzzer_impl_.stats_, [] { return absl::Now(); });
741718
}
719+
if (runner_mode) {
720+
runtime_.RegisterCrashMetadataListener(
721+
[](absl::string_view crash_type,
722+
absl::Span<const std::string> /*stack_frames*/) {
723+
CentipedeSetFailureDescription(std::string{crash_type}.c_str());
724+
});
725+
}
742726
if (!configuration.corpus_database.empty() &&
743727
configuration.crashing_input_to_reproduce.has_value() &&
744728
configuration.replay_in_single_process) {

0 commit comments

Comments
 (0)