Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions centipede/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,7 @@ cc_test(
srcs = ["centipede_test.cc"],
data = [
"@com_google_fuzztest//centipede/testing:abort_fuzz_target",
"@com_google_fuzztest//centipede/testing:async_failing_target",
"@com_google_fuzztest//centipede/testing:expensive_startup_fuzz_target",
"@com_google_fuzztest//centipede/testing:fuzz_target_with_config",
"@com_google_fuzztest//centipede/testing:fuzz_target_with_custom_mutator",
Expand Down
19 changes: 15 additions & 4 deletions centipede/centipede_default_callbacks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,12 @@ CentipedeDefaultCallbacks::GetSerializedTargetConfig() {
std::vector<ByteArray> CentipedeDefaultCallbacks::Mutate(
const std::vector<MutationInputRef> &inputs, size_t num_mutants) {
if (num_mutants == 0) return {};
// Try to use the custom mutator if it hasn't been disabled.
if (custom_mutator_is_usable_.value_or(true)) {
// In persistent mode, mutation could fail due to previous asynchronous
// failure, thus give it one more chance to mutate in a clean state.
for (int num_attempts = env_.persistent_mode ? 2 : 1; num_attempts > 0;
--num_attempts) {
// Do not use the custom mutator if it has been disabled.
if (!custom_mutator_is_usable_.value_or(true)) break;
MutationResult result =
MutateViaExternalBinary(env_.binary, inputs, num_mutants);
if (result.exit_code() == EXIT_SUCCESS) {
Expand All @@ -100,17 +104,24 @@ std::vector<ByteArray> CentipedeDefaultCallbacks::Mutate(
<< "Custom mutator returned no mutants; will "
"generate some using the built-in mutator.";
}
break;
} else if (ShouldStop()) {
FUZZTEST_LOG(WARNING)
<< "Custom mutator failed, but ignored since the stop "
"condition it met. Possibly what triggered the stop "
"condition also interrupted the mutator.";
// Returning whatever mutants we got before the failure.
return std::move(result).mutants();
} else if (num_attempts > 1) {
// Failed to mutate but still has more attempts
CleanUpPersistentMode();
FUZZTEST_LOG(ERROR)
<< "Test binary failed to mutate inputs - clean up and try again.";
} else {
// Still failing at the final attempt.
PrintExecutionLog();
FUZZTEST_LOG(ERROR)
<< "Test binary failed when asked to mutate inputs - exiting.";
FUZZTEST_LOG(ERROR) << "Test binary failed to mutate inputs at the final "
"attempt - exiting.";
RequestEarlyStop(EXIT_FAILURE);
return {};
}
Expand Down
25 changes: 25 additions & 0 deletions centipede/centipede_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1234,5 +1234,30 @@ TEST_F(CentipedeWithTemporaryLocalDir, ExecuteEndsAfterCustomFailure) {
Not(HasSubstr("custom failure 2"))));
}

TEST_F(CentipedeWithTemporaryLocalDir, TolerateAsyncFailureInMutation) {
Environment env;
env.binary =
GetDataDependencyFilepath("centipede/testing/async_failing_target");
CentipedeDefaultCallbacks callbacks(env);
BatchResult result;
std::vector<ByteArray> inputs = {
{'s', 'o', 'm', 'e'},
};
ClearEarlyStopRequestAndSetStopTime(absl::InfiniteFuture());
EXPECT_TRUE(callbacks.Execute(env.binary, inputs, result));
// Match the error log to check for retrying mutation.
EXPECT_DEATH(
[&] {
callbacks.Mutate(GetMutationInputRefsFromDataInputs(inputs),
inputs.size());
FUZZTEST_LOG(INFO) << "Mutate() succeeded";
std::_Exit(EXIT_FAILURE);
}(),
AllOf(
HasSubstr(
"Test binary failed to mutate inputs - clean up and try again."),
HasSubstr("Mutate() succeeded")));
}

} // namespace
} // namespace fuzztest::internal
21 changes: 21 additions & 0 deletions centipede/testing/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,27 @@ centipede_fuzz_target(
fuzz_target = "_external_target_server",
)

# Binary for :async_failing_target.
cc_binary(
name = "_async_failing_target",
srcs = ["async_failing_target.cc"],
# Cannot be built directly - build :async_failing_target instead.
tags = [
"local",
"manual",
"notap",
],
deps = [
"@com_google_fuzztest//centipede:centipede_runner_no_main",
"@com_google_fuzztest//common:defs",
],
)

centipede_fuzz_target(
name = "async_failing_target",
fuzz_target = "_async_failing_target",
)

cc_binary(
name = "_external_target",
srcs = ["external_target.cc"],
Expand Down
52 changes: 52 additions & 0 deletions centipede/testing/async_failing_target.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2026 The Centipede Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cstdlib>

#include "./centipede/runner_interface.h"
#include "./common/defs.h"

namespace {

class AsyncFailingTargetRunnerCallbacks
: public fuzztest::internal::RunnerCallbacks {
public:
bool Execute(fuzztest::internal::ByteSpan input) override {
to_fail_in_mutation = true;
return true;
}

bool Mutate(const std::vector<fuzztest::internal::MutationInputRef>& inputs,
size_t num_mutants,
std::function<void(fuzztest::internal::ByteSpan)>
new_mutant_callback) override {
if (to_fail_in_mutation) {
fprintf(stderr, "Fail in mutation\n");
std::abort();
}
return true;
}

bool HasCustomMutator() const override { return true; }

private:
bool to_fail_in_mutation = false;
};

} // namespace

int main(int argc, char** absl_nonnull argv) {
AsyncFailingTargetRunnerCallbacks runner_callbacks;
return fuzztest::internal::RunnerMain(argc, argv, runner_callbacks);
}
Loading