NFC: Rename mutation_input.h to mutation_data.h to include the output types.

This is to enable mutator to return extra information with the mutants, as such the origins, which are needed for input reduction and entropy-based corpus scheduling.

PiperOrigin-RevId: 877418585
diff --git a/centipede/BUILD b/centipede/BUILD
index 4e1de94..15fc743 100644
--- a/centipede/BUILD
+++ b/centipede/BUILD
@@ -369,7 +369,7 @@
     deps = [
         ":centipede_callbacks",
         ":environment",
-        ":mutation_input",
+        ":mutation_data",
         ":runner_result",
         ":stop",
         ":thread_pool",
@@ -442,6 +442,7 @@
         # used in centipede_runner.
         ":feature",
         ":execution_metadata",
+        ":mutation_data",
         ":shared_memory_blob_sequence",
         "@com_google_fuzztest//common:defs",
     ],
@@ -457,14 +458,14 @@
         # used in centipede_runner.
         ":shared_memory_blob_sequence",
         ":execution_metadata",
-        ":mutation_input",
+        ":mutation_data",
         "@com_google_fuzztest//common:defs",
     ],
 )
 
 cc_library(
-    name = "mutation_input",
-    hdrs = ["mutation_input.h"],
+    name = "mutation_data",
+    hdrs = ["mutation_data.h"],
     copts = DISABLE_SANCOV_COPTS,
     deps = [
         # This target must have a minimal set of dependencies since it is
@@ -483,7 +484,7 @@
     deps = [
         ":execution_metadata",
         ":knobs",
-        ":mutation_input",
+        ":mutation_data",
         "@abseil-cpp//absl/base:nullability",
         "@com_google_fuzztest//common:defs",
     ],
@@ -627,7 +628,7 @@
         ":control_flow",
         ":environment",
         ":fuzztest_mutator",
-        ":mutation_input",
+        ":mutation_data",
         ":runner_request",
         ":runner_result",
         ":shared_memory_blob_sequence",
@@ -719,7 +720,7 @@
         ":environment",
         ":feature",
         ":feature_set",
-        ":mutation_input",
+        ":mutation_data",
         ":pc_info",
         ":runner_result",
         ":rusage_profiler",
@@ -855,7 +856,7 @@
     deps = [
         ":centipede_callbacks",
         ":environment",
-        ":mutation_input",
+        ":mutation_data",
         ":runner_result",
         ":stop",
         "@abseil-cpp//absl/status",
@@ -873,7 +874,7 @@
         ":byte_array_mutator",
         ":execution_metadata",
         ":knobs",
-        ":mutation_input",
+        ":mutation_data",
         "@abseil-cpp//absl/random",
         "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//common:defs",
@@ -958,6 +959,7 @@
     hdrs = ["dispatcher.h"],
     deps = [
         ":execution_metadata",
+        ":mutation_data",
         ":runner_request",
         ":runner_result",
         ":shared_memory_blob_sequence",
@@ -1033,7 +1035,7 @@
     ":foreach_nonzero",
     ":int_utils",
     ":knobs",
-    ":mutation_input",
+    ":mutation_data",
     ":rolling_hash",
     ":runner_cmp_trace",
     ":runner_fork_server",
@@ -1098,7 +1100,7 @@
     linkstatic = True,  # Must be linked statically even when dynamic_mode=on.
     deps = [
         ":centipede_runner_no_main",
-        ":mutation_input",
+        ":mutation_data",
         "@abseil-cpp//absl/base:nullability",
         "@com_google_fuzztest//common:defs",
     ],
@@ -1237,7 +1239,7 @@
         ":corpus",
         ":environment",
         ":feature",
-        ":mutation_input",
+        ":mutation_data",
         ":runner_result",
         ":util",
         "@com_google_fuzztest//common:defs",
@@ -1509,6 +1511,7 @@
     deps = [
         ":execution_metadata",
         ":feature",
+        ":mutation_data",
         ":runner_result",
         ":shared_memory_blob_sequence",
         "@com_google_fuzztest//common:defs",
@@ -1518,10 +1521,10 @@
 )
 
 cc_test(
-    name = "mutation_input_test",
-    srcs = ["mutation_input_test.cc"],
+    name = "mutation_data_test",
+    srcs = ["mutation_data_test.cc"],
     deps = [
-        ":mutation_input",
+        ":mutation_data",
         "@com_google_fuzztest//common:defs",
         "@googletest//:gtest_main",
     ],
@@ -1534,7 +1537,7 @@
         ":byte_array_mutator",
         ":execution_metadata",
         ":knobs",
-        ":mutation_input",
+        ":mutation_data",
         ":runner_cmp_trace",
         "@abseil-cpp//absl/container:flat_hash_set",
         "@com_google_fuzztest//common:defs",
@@ -1567,7 +1570,7 @@
         ":execution_metadata",
         ":fuzztest_mutator",
         ":knobs",
-        ":mutation_input",
+        ":mutation_data",
         "@abseil-cpp//absl/container:flat_hash_set",
         "@abseil-cpp//absl/strings",
         "@com_google_fuzztest//common:defs",
@@ -1914,7 +1917,7 @@
         ":centipede_interface",
         ":environment",
         ":feature",
-        ":mutation_input",
+        ":mutation_data",
         ":runner_result",
         ":stop",
         ":util",
diff --git a/centipede/byte_array_mutator.cc b/centipede/byte_array_mutator.cc
index 9a29a43..60834c7 100644
--- a/centipede/byte_array_mutator.cc
+++ b/centipede/byte_array_mutator.cc
@@ -24,7 +24,7 @@
 
 #include "./centipede/execution_metadata.h"
 #include "./centipede/knobs.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./common/defs.h"
 
 namespace fuzztest::internal {
@@ -321,27 +321,28 @@
 // TODO(kcc): add tests with different values of knobs.
 const KnobId knob_mutate_or_crossover = Knobs::NewId("mutate_or_crossover");
 
-std::vector<ByteArray> ByteArrayMutator::MutateMany(
-    const std::vector<MutationInputRef> &inputs, size_t num_mutants) {
+std::vector<Mutant> ByteArrayMutator::MutateMany(
+    const std::vector<MutationInputRef>& inputs, size_t num_mutants) {
   if (inputs.empty()) abort();
   // TODO(xinhaoyuan): Consider metadata in other inputs instead of always the
   // first one.
   SetMetadata(inputs[0].metadata != nullptr ? *inputs[0].metadata
                                             : ExecutionMetadata());
   size_t num_inputs = inputs.size();
-  std::vector<ByteArray> mutants;
+  std::vector<Mutant> mutants;
   mutants.reserve(num_mutants);
   for (size_t i = 0; i < num_mutants; ++i) {
-    auto mutant = inputs[rng_() % num_inputs].data;
-    if (mutant.size() <= max_len_ &&
+    Mutant mutant;
+    mutant.data = inputs[rng_() % num_inputs].data;
+    if (mutant.data.size() <= max_len_ &&
         knobs_.GenerateBool(knob_mutate_or_crossover, rng_())) {
       // Do crossover only if the mutant is not over the max_len_.
       // Perform crossover with some other input. It may be the same input.
       const auto &other_input = inputs[rng_() % num_inputs].data;
-      CrossOver(mutant, other_input);
+      CrossOver(mutant.data, other_input);
     } else {
       // Perform mutation.
-      Mutate(mutant);
+      Mutate(mutant.data);
     }
     mutants.push_back(std::move(mutant));
   }
diff --git a/centipede/byte_array_mutator.h b/centipede/byte_array_mutator.h
index 3c6978c..cec8208 100644
--- a/centipede/byte_array_mutator.h
+++ b/centipede/byte_array_mutator.h
@@ -25,7 +25,7 @@
 #include "absl/base/nullability.h"
 #include "./centipede/execution_metadata.h"
 #include "./centipede/knobs.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./common/defs.h"
 
 namespace fuzztest::internal {
@@ -108,8 +108,8 @@
   }
 
   // Takes non-empty `inputs` and produces `num_mutants` mutants.
-  std::vector<ByteArray> MutateMany(const std::vector<MutationInputRef> &inputs,
-                                    size_t num_mutants);
+  std::vector<Mutant> MutateMany(const std::vector<MutationInputRef>& inputs,
+                                 size_t num_mutants);
 
   using CrossOverFn = void (ByteArrayMutator::*)(ByteArray &,
                                                  const ByteArray &);
diff --git a/centipede/byte_array_mutator_test.cc b/centipede/byte_array_mutator_test.cc
index ae35641..7c20d48 100644
--- a/centipede/byte_array_mutator_test.cc
+++ b/centipede/byte_array_mutator_test.cc
@@ -25,7 +25,7 @@
 #include "absl/container/flat_hash_set.h"
 #include "./centipede/execution_metadata.h"
 #include "./centipede/knobs.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/runner_cmp_trace.h"
 #include "./common/defs.h"
 
@@ -928,12 +928,12 @@
       {0, 1, 2, 3, 4, 5, 6, 7},
       {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
   };
-  const std::vector<ByteArray> mutants =
+  const std::vector<Mutant> mutants =
       mutator.MutateMany(GetMutationInputRefsFromDataInputs(aligned_inputs),
                          kNumMutantsToGenerate);
   EXPECT_EQ(mutants.size(), kNumMutantsToGenerate);
-  for (const ByteArray &mutant : mutants) {
-    EXPECT_EQ(mutant.size() % kSizeAlignment, 0);
+  for (const Mutant& mutant : mutants) {
+    EXPECT_EQ(mutant.data.size() % kSizeAlignment, 0);
   }
 }
 
@@ -958,13 +958,13 @@
       {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
       {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
   };
-  const std::vector<ByteArray> mutants =
+  const std::vector<Mutant> mutants =
       mutator.MutateMany(GetMutationInputRefsFromDataInputs(unaligned_inputs),
                          kNumMutantsToGenerate);
   EXPECT_EQ(mutants.size(), kNumMutantsToGenerate);
-  for (const ByteArray &mutant : mutants) {
-    if (mutant.size() % kSizeAlignment != 0) {
-      EXPECT_LE(mutant.size(), 11);
+  for (const Mutant& mutant : mutants) {
+    if (mutant.data.size() % kSizeAlignment != 0) {
+      EXPECT_LE(mutant.data.size(), 11);
     }
   }
 }
@@ -982,12 +982,12 @@
       {0, 1, 2},
       {0, 1, 2, 3},
   };
-  const std::vector<ByteArray> mutants = mutator.MutateMany(
+  const std::vector<Mutant> mutants = mutator.MutateMany(
       GetMutationInputRefsFromDataInputs(inputs), kNumMutantsToGenerate);
   EXPECT_EQ(mutants.size(), kNumMutantsToGenerate);
 
-  for (const ByteArray &mutant : mutants) {
-    EXPECT_LE(mutant.size(), kMaxLen);
+  for (const Mutant& mutant : mutants) {
+    EXPECT_LE(mutant.data.size(), kMaxLen);
   }
 }
 
@@ -1001,16 +1001,16 @@
   const std::vector<ByteArray> large_input = {
       {0, 1, 2, 3, 4, 5, 6, 7}, {0}, {0, 1}, {0, 1, 2}, {0, 1, 2, 3},
   };
-  const std::vector<ByteArray> mutants = mutator.MutateMany(
+  const std::vector<Mutant> mutants = mutator.MutateMany(
       GetMutationInputRefsFromDataInputs(large_input), kNumMutantsToGenerate);
   EXPECT_EQ(mutants.size(), kNumMutantsToGenerate);
 
-  for (const ByteArray &mutant : mutants) {
-    if (mutant.size() > kMaxLen) {
+  for (const Mutant& mutant : mutants) {
+    if (mutant.data.size() > kMaxLen) {
       // The only mutant larger than max length should be the same large input
       // that mutation originally started with. All other mutants should be
       // within the maximum length specified.
-      EXPECT_EQ(mutant, large_input[0]);
+      EXPECT_EQ(mutant.data, large_input[0]);
     }
   }
 }
diff --git a/centipede/centipede.cc b/centipede/centipede.cc
index b18f995..2dab183 100644
--- a/centipede/centipede.cc
+++ b/centipede/centipede.cc
@@ -82,7 +82,7 @@
 #include "./centipede/environment.h"
 #include "./centipede/feature.h"
 #include "./centipede/feature_set.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/runner_result.h"
 #include "./centipede/rusage_profiler.h"
 #include "./centipede/rusage_stats.h"
@@ -427,10 +427,10 @@
 }
 
 bool Centipede::RunBatch(
-    const std::vector<ByteArray> &input_vec,
-    BlobFileWriter *absl_nullable corpus_file,
-    BlobFileWriter *absl_nullable features_file,
-    BlobFileWriter *absl_nullable unconditional_features_file) {
+    const std::vector<ByteArray>& input_vec,
+    BlobFileWriter* absl_nullable corpus_file,
+    BlobFileWriter* absl_nullable features_file,
+    BlobFileWriter* absl_nullable unconditional_features_file) {
   BatchResult batch_result;
   bool success = ExecuteAndReportCrash(env_.binary, input_vec, batch_result);
   FUZZTEST_CHECK_EQ(input_vec.size(), batch_result.results().size());
@@ -868,14 +868,19 @@
           MutationInputRef{corpus_record.data, &corpus_record.metadata});
     }
 
-    const std::vector<ByteArray> mutants =
+    std::vector<Mutant> mutants =
         user_callbacks_.Mutate(mutation_inputs, batch_size);
     if (ShouldStop()) break;
-
-    bool gained_new_coverage =
-        RunBatch(mutants, corpus_file.get(), features_file.get(), nullptr);
     new_runs += mutants.size();
 
+    std::vector<ByteArray> inputs;
+    inputs.reserve(mutants.size());
+    for (auto& mutant : mutants) {
+      inputs.push_back(std::move(mutant.data));
+    }
+    bool gained_new_coverage =
+        RunBatch(inputs, corpus_file.get(), features_file.get(), nullptr);
+
     if (gained_new_coverage) {
       UpdateAndMaybeLogStats("new-feature", 1);
     } else if (((batch_index - 1) & batch_index) == 0) {
diff --git a/centipede/centipede.h b/centipede/centipede.h
index 625938f..aa25a42 100644
--- a/centipede/centipede.h
+++ b/centipede/centipede.h
@@ -84,10 +84,10 @@
   //   * its features are written to `features_file` (if that's non-null).
   // Returns true if new features were observed.
   // Post-condition: `batch_result.results.size()` == `input_vec.size()`.
-  bool RunBatch(const std::vector<ByteArray> &input_vec,
-                BlobFileWriter *absl_nullable corpus_file,
-                BlobFileWriter *absl_nullable features_file,
-                BlobFileWriter *absl_nullable unconditional_features_file);
+  bool RunBatch(const std::vector<ByteArray>& input_vec,
+                BlobFileWriter* absl_nullable corpus_file,
+                BlobFileWriter* absl_nullable features_file,
+                BlobFileWriter* absl_nullable unconditional_features_file);
   // Loads seed inputs from the user callbacks, execute them, and store them
   // with the corresponding features into `corpus_file` and `features_file`.
   void LoadSeedInputs(BlobFileWriter *absl_nonnull corpus_file,
diff --git a/centipede/centipede_callbacks.cc b/centipede/centipede_callbacks.cc
index 14e11e8..66c6c6c 100644
--- a/centipede/centipede_callbacks.cc
+++ b/centipede/centipede_callbacks.cc
@@ -48,7 +48,7 @@
 #include "./centipede/binary_info.h"
 #include "./centipede/command.h"
 #include "./centipede/control_flow.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/runner_request.h"
 #include "./centipede/runner_result.h"
 #include "./centipede/stop.h"
diff --git a/centipede/centipede_callbacks.h b/centipede/centipede_callbacks.h
index 0a946f1..181fd2a 100644
--- a/centipede/centipede_callbacks.h
+++ b/centipede/centipede_callbacks.h
@@ -30,7 +30,7 @@
 #include "./centipede/command.h"
 #include "./centipede/environment.h"
 #include "./centipede/fuzztest_mutator.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/runner_result.h"
 #include "./centipede/shared_memory_blob_sequence.h"
 #include "./centipede/util.h"
@@ -72,8 +72,8 @@
                        BatchResult &batch_result) = 0;
 
   // Takes non-empty `inputs` and returns at most `num_mutants` mutated inputs.
-  virtual std::vector<ByteArray> Mutate(
-      const std::vector<MutationInputRef> &inputs, size_t num_mutants) {
+  virtual std::vector<Mutant> Mutate(
+      const std::vector<MutationInputRef>& inputs, size_t num_mutants) {
     return env_.use_legacy_default_mutator
                ? byte_array_mutator_.MutateMany(inputs, num_mutants)
                : fuzztest_mutator_.MutateMany(inputs, num_mutants);
diff --git a/centipede/centipede_default_callbacks.cc b/centipede/centipede_default_callbacks.cc
index 73b8b8e..67fe09f 100644
--- a/centipede/centipede_default_callbacks.cc
+++ b/centipede/centipede_default_callbacks.cc
@@ -25,7 +25,7 @@
 #include "absl/status/statusor.h"
 #include "./centipede/centipede_callbacks.h"
 #include "./centipede/environment.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/runner_result.h"
 #include "./centipede/stop.h"
 #include "./common/defs.h"
@@ -72,8 +72,8 @@
       "Failed to get serialized configuration from the target binary.");
 }
 
-std::vector<ByteArray> CentipedeDefaultCallbacks::Mutate(
-    const std::vector<MutationInputRef> &inputs, size_t num_mutants) {
+std::vector<Mutant> CentipedeDefaultCallbacks::Mutate(
+    const std::vector<MutationInputRef>& inputs, size_t num_mutants) {
   if (num_mutants == 0) return {};
   // In persistent mode, mutation could fail due to previous asynchronous
   // failure, thus give it one more chance to mutate in a clean state.
diff --git a/centipede/centipede_default_callbacks.h b/centipede/centipede_default_callbacks.h
index 0b78562..9fb157f 100644
--- a/centipede/centipede_default_callbacks.h
+++ b/centipede/centipede_default_callbacks.h
@@ -28,7 +28,7 @@
 #include "absl/status/statusor.h"
 #include "./centipede/centipede_callbacks.h"
 #include "./centipede/environment.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/runner_result.h"
 #include "./common/defs.h"
 
@@ -42,8 +42,8 @@
   absl::StatusOr<std::string> GetSerializedTargetConfig() override;
   bool Execute(std::string_view binary, const std::vector<ByteArray> &inputs,
                BatchResult &batch_result) override;
-  std::vector<ByteArray> Mutate(const std::vector<MutationInputRef> &inputs,
-                                size_t num_mutants) override;
+  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+                             size_t num_mutants) override;
 
  private:
   std::optional<bool> custom_mutator_is_usable_ = std::nullopt;
diff --git a/centipede/centipede_test.cc b/centipede/centipede_test.cc
index 16e928f..9454d34 100644
--- a/centipede/centipede_test.cc
+++ b/centipede/centipede_test.cc
@@ -38,7 +38,7 @@
 #include "./centipede/centipede_interface.h"
 #include "./centipede/environment.h"
 #include "./centipede/feature.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/runner_result.h"
 #include "./centipede/stop.h"
 #include "./centipede/util.h"
@@ -106,19 +106,19 @@
   // (the value {0} is produced by the default GetSeeds()).
   // Next 65536 mutations are 2-byte sequences {0,0} ... {255, 255}.
   // Then repeat 2-byte sequences.
-  std::vector<ByteArray> Mutate(const std::vector<MutationInputRef> &inputs,
-                                size_t num_mutants) override {
-    std::vector<ByteArray> mutants;
+  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+                             size_t num_mutants) override {
+    std::vector<Mutant> mutants;
     mutants.reserve(num_mutants);
     for (size_t i = 0; i < num_mutants; ++i) {
       num_mutations_++;
       if (num_mutations_ < 256) {
-        mutants.push_back({static_cast<uint8_t>(num_mutations_)});
+        mutants.push_back({/*data=*/{static_cast<uint8_t>(num_mutations_)}});
         continue;
       }
       uint8_t byte0 = (num_mutations_ - 256) / 256;
       uint8_t byte1 = (num_mutations_ - 256) % 256;
-      mutants.push_back({byte0, byte1});
+      mutants.push_back({/*data=*/{byte0, byte1}});
     }
     return mutants;
   }
@@ -350,8 +350,8 @@
   }
 
   // Will not be called.
-  std::vector<ByteArray> Mutate(const std::vector<MutationInputRef> &inputs,
-                                size_t num_mutants) override {
+  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+                             size_t num_mutants) override {
     FUZZTEST_LOG(FATAL);
   }
 
@@ -436,8 +436,9 @@
           GetMutationInputRefsFromDataInputs(inputs), 10000);
       EXPECT_EQ(result.exit_code(), EXIT_SUCCESS);
       EXPECT_TRUE(result.has_custom_mutator());
-      EXPECT_THAT(result.mutants(), AllOf(IsSupersetOf(all_expected_mutants),
-                                          Each(Not(IsEmpty()))));
+      EXPECT_THAT(
+          GetDataFromMutants(result.mutants()),
+          AllOf(IsSupersetOf(all_expected_mutants), Each(Not(IsEmpty()))));
     }
   }
 
@@ -451,9 +452,10 @@
         10000);
     EXPECT_EQ(result.exit_code(), EXIT_SUCCESS);
     EXPECT_TRUE(result.has_custom_mutator());
-    EXPECT_THAT(result.mutants(), AllOf(IsSupersetOf(all_expected_mutants),
-                                        Each(Not(IsEmpty()))));
-    EXPECT_THAT(result.mutants(),
+    const auto mutant_data = GetDataFromMutants(result.mutants());
+    EXPECT_THAT(mutant_data, AllOf(IsSupersetOf(all_expected_mutants),
+                                   Each(Not(IsEmpty()))));
+    EXPECT_THAT(mutant_data,
                 AllOf(IsSupersetOf(all_expected_mutants), Each(Not(IsEmpty())),
                       // The byte_array_mutator may insert up to 20 bytes to an
                       // input, which may push the size over the max_len.
@@ -471,9 +473,10 @@
             binary_with_custom_mutator,
             GetMutationInputRefsFromDataInputs(inputs), 10000);
     // Must contain normal mutants, but not the ones from crossover.
-    EXPECT_THAT(result.mutants(), IsSupersetOf(some_of_expected_mutants));
+    const auto mutant_data = GetDataFromMutants(result.mutants());
+    EXPECT_THAT(mutant_data, IsSupersetOf(some_of_expected_mutants));
     for (const auto &crossover_mutant : expected_crossover_mutants) {
-      EXPECT_THAT(result.mutants(), Not(Contains(crossover_mutant)));
+      EXPECT_THAT(mutant_data, Not(Contains(crossover_mutant)));
     }
   }
 }
@@ -497,12 +500,12 @@
   }
 
   // Every consecutive mutation is {number_of_mutations_} (starting from 1).
-  std::vector<ByteArray> Mutate(const std::vector<MutationInputRef> &inputs,
-                                size_t num_mutants) override {
-    std::vector<ByteArray> mutants{num_mutants};
+  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+                             size_t num_mutants) override {
+    std::vector<Mutant> mutants(num_mutants);
     for (auto &mutant : mutants) {
-      mutant.resize(1);
-      mutant[0] = ++number_of_mutations_;
+      mutant.data.resize(1);
+      mutant.data[0] = ++number_of_mutations_;
     }
     return mutants;
   }
@@ -582,17 +585,17 @@
   }
 
   // Sets the inputs to one of 3 pre-defined values.
-  std::vector<ByteArray> Mutate(const std::vector<MutationInputRef> &inputs,
-                                size_t num_mutants) override {
+  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+                             size_t num_mutants) override {
     for (auto &input : inputs) {
       if (!seed_inputs_.contains(input.data)) {
         observed_inputs_.insert(input.data);
       }
     }
-    std::vector<ByteArray> mutants;
+    std::vector<Mutant> mutants;
     mutants.reserve(num_mutants);
     for (size_t i = 0; i < num_mutants; ++i) {
-      mutants.push_back(GetMutant(++number_of_mutations_));
+      mutants.push_back({/*data=*/GetMutant(++number_of_mutations_)});
     }
     return mutants;
   }
@@ -703,12 +706,12 @@
   }
 
   // Sets the mutants to different 1-byte values.
-  std::vector<ByteArray> Mutate(const std::vector<MutationInputRef> &inputs,
-                                size_t num_mutants) override {
-    std::vector<ByteArray> mutants{num_mutants};
+  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+                             size_t num_mutants) override {
+    std::vector<Mutant> mutants(num_mutants);
     for (auto &mutant : mutants) {
-      mutant.resize(1);
-      mutant[0] = ++number_of_mutations_;
+      mutant.data.resize(1);
+      mutant.data[0] = ++number_of_mutations_;
     }
     return mutants;
   }
@@ -836,13 +839,13 @@
   }
 
   // Sets the mutants to different 1-byte values.
-  std::vector<ByteArray> Mutate(const std::vector<MutationInputRef> &inputs,
-                                size_t num_mutants) override {
-    std::vector<ByteArray> mutants;
+  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+                             size_t num_mutants) override {
+    std::vector<Mutant> mutants;
     mutants.reserve(num_mutants);
     for (size_t i = 0; i < num_mutants; ++i) {
       // The contents of each mutant is simply its sequential number.
-      mutants.push_back({static_cast<uint8_t>(curr_input_idx_++)});
+      mutants.push_back({/*data=*/{static_cast<uint8_t>(curr_input_idx_++)}});
     }
     return mutants;
   }
@@ -996,9 +999,9 @@
     return true;
   }
 
-  std::vector<ByteArray> Mutate(const std::vector<MutationInputRef> &inputs,
-                                size_t num_mutants) override {
-    return {num_mutants, {0}};
+  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+                             size_t num_mutants) override {
+    return {num_mutants, {/*data=*/{0}}};
   }
 
   bool thread_check_passed() { return thread_check_passed_; }
@@ -1051,9 +1054,9 @@
     return false;
   }
 
-  std::vector<ByteArray> Mutate(const std::vector<MutationInputRef> &inputs,
-                                size_t num_mutants) override {
-    return {num_mutants, {0}};
+  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+                             size_t num_mutants) override {
+    return {num_mutants, {/*data=*/{0}}};
   }
 
   int execute_count() const { return execute_count_; }
@@ -1089,9 +1092,9 @@
     return false;
   }
 
-  std::vector<ByteArray> Mutate(const std::vector<MutationInputRef> &inputs,
-                                size_t num_mutants) override {
-    return {num_mutants, {0}};
+  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+                             size_t num_mutants) override {
+    return {num_mutants, {/*data=*/{0}}};
   }
 
   int execute_count() const { return execute_count_; }
@@ -1127,9 +1130,9 @@
     return false;
   }
 
-  std::vector<ByteArray> Mutate(const std::vector<MutationInputRef> &inputs,
-                                size_t num_mutants) override {
-    return {num_mutants, {0}};
+  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+                             size_t num_mutants) override {
+    return {num_mutants, {/*data=*/{0}}};
   }
 
   int execute_count() const { return execute_count_; }
@@ -1160,11 +1163,11 @@
   CentipedeDefaultCallbacks callbacks(env);
 
   const std::vector<ByteArray> inputs = {{1}, {2}, {3}, {4}, {5}, {6}};
-  const std::vector<ByteArray> mutants = callbacks.Mutate(
+  const std::vector<Mutant> mutants = callbacks.Mutate(
       GetMutationInputRefsFromDataInputs(inputs), inputs.size());
 
   // The custom mutator just returns the original inputs as mutants.
-  EXPECT_EQ(inputs, mutants);
+  EXPECT_EQ(inputs, GetDataFromMutants(mutants));
 }
 
 TEST_F(CentipedeWithTemporaryLocalDir, FailsOnMisbehavingCustomMutator) {
@@ -1193,12 +1196,12 @@
   CentipedeDefaultCallbacks callbacks(env);
 
   const std::vector<ByteArray> inputs = {{1}, {2}, {3}, {4}, {5}, {6}};
-  const std::vector<ByteArray> mutants = callbacks.Mutate(
+  const std::vector<Mutant> mutants = callbacks.Mutate(
       GetMutationInputRefsFromDataInputs(inputs), inputs.size());
 
   // The built-in mutator performs non-trivial mutations.
   EXPECT_EQ(inputs.size(), mutants.size());
-  EXPECT_NE(inputs, mutants);
+  EXPECT_NE(inputs, GetDataFromMutants(mutants));
 }
 
 TEST_F(CentipedeWithTemporaryLocalDir,
diff --git a/centipede/dispatcher.cc b/centipede/dispatcher.cc
index d82cd28..6980b35 100644
--- a/centipede/dispatcher.cc
+++ b/centipede/dispatcher.cc
@@ -31,6 +31,7 @@
 
 #include "absl/base/nullability.h"
 #include "./centipede/execution_metadata.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/runner_request.h"
 #include "./centipede/runner_result.h"
 #include "./centipede/shared_memory_blob_sequence.h"
@@ -433,6 +434,7 @@
 using fuzztest::internal::kDispatcherTestGetSeedsOutputDirFlagHeader;
 using fuzztest::internal::kDispatcherTestListingPrefixFlagHeader;
 using fuzztest::internal::kDispatcherTestNameFlagHeader;
+using fuzztest::internal::MutantRef;
 using fuzztest::internal::MutationResult;
 
 int FuzzTestDispatcherIsEnabled() {
@@ -535,9 +537,10 @@
                   "mutant must be non-empty with a valid pointer");
   auto* output = GetOutputsBlobSequence();
   DispatcherCheck(output != nullptr, "outputs blob sequence must exist");
-  DispatcherCheck(MutationResult::WriteMutant(
-                      {static_cast<const uint8_t*>(data), size}, *output),
-                  "failed to write mutant");
+  DispatcherCheck(
+      MutationResult::WriteMutant(
+          MutantRef{{static_cast<const uint8_t*>(data), size}}, *output),
+      "failed to write mutant");
 }
 
 void FuzzTestDispatcherEmitFeedbackAs32BitFeatures(const uint32_t* features,
diff --git a/centipede/fuzztest_mutator.cc b/centipede/fuzztest_mutator.cc
index 0690f28..6985fdf 100644
--- a/centipede/fuzztest_mutator.cc
+++ b/centipede/fuzztest_mutator.cc
@@ -29,7 +29,7 @@
 #include "./centipede/byte_array_mutator.h"
 #include "./centipede/execution_metadata.h"
 #include "./centipede/knobs.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./common/defs.h"
 #include "./common/logging.h"
 #include "./fuzztest/domain_core.h"
@@ -139,12 +139,12 @@
   }
 }
 
-std::vector<ByteArray> FuzzTestMutator::MutateMany(
-    const std::vector<MutationInputRef> &inputs, size_t num_mutants) {
+std::vector<Mutant> FuzzTestMutator::MutateMany(
+    const std::vector<MutationInputRef>& inputs, size_t num_mutants) {
   if (inputs.empty()) abort();
   auto& cmp_tables = mutation_metadata_->cmp_tables;
   cmp_tables.resize(inputs.size());
-  std::vector<ByteArray> mutants;
+  std::vector<Mutant> mutants;
   mutants.reserve(num_mutants);
   for (int i = 0; i < num_mutants; ++i) {
     auto index = absl::Uniform<size_t>(prng_, 0, inputs.size());
@@ -152,16 +152,16 @@
       cmp_tables[index].emplace(/*compact=*/true);
       PopulateCmpEntries(*inputs[index].metadata, *cmp_tables[index]);
     }
-    auto mutant = inputs[index].data;
-    if (mutant.size() > max_len_) mutant.resize(max_len_);
+    Mutant mutant = {/*data=*/inputs[index].data};
+    if (mutant.data.size() > max_len_) mutant.data.resize(max_len_);
     if (knobs_.GenerateBool(knob_mutate_or_crossover, prng_())) {
       // Perform crossover with some other input. It may be the same input.
       const auto &other_input =
           inputs[absl::Uniform<size_t>(prng_, 0, inputs.size())].data;
-      CrossOver(mutant, other_input);
+      CrossOver(mutant.data, other_input);
     } else {
       domain_->Mutate(
-          mutant, prng_,
+          mutant.data, prng_,
           {/*cmp_tables=*/cmp_tables[index].has_value() ? &*cmp_tables[index]
                                                         : nullptr},
           /*only_shrink=*/false);
diff --git a/centipede/fuzztest_mutator.h b/centipede/fuzztest_mutator.h
index 8b68460..ff204f2 100644
--- a/centipede/fuzztest_mutator.h
+++ b/centipede/fuzztest_mutator.h
@@ -22,7 +22,7 @@
 
 #include "./centipede/execution_metadata.h"
 #include "./centipede/knobs.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./common/defs.h"
 #include "./fuzztest/internal/table_of_recent_compares.h"
 
@@ -44,8 +44,8 @@
   ~FuzzTestMutator();
 
   // Takes non-empty `inputs` and produces `num_mutants` mutants.
-  std::vector<ByteArray> MutateMany(const std::vector<MutationInputRef> &inputs,
-                                    size_t num_mutants);
+  std::vector<Mutant> MutateMany(const std::vector<MutationInputRef>& inputs,
+                                 size_t num_mutants);
 
   // Adds `dict_entries` to the internal mutation dictionary.
   void AddToDictionary(const std::vector<ByteArray>& dict_entries);
diff --git a/centipede/fuzztest_mutator_test.cc b/centipede/fuzztest_mutator_test.cc
index 327bc83..6c4d747 100644
--- a/centipede/fuzztest_mutator_test.cc
+++ b/centipede/fuzztest_mutator_test.cc
@@ -24,7 +24,7 @@
 #include "absl/strings/str_join.h"
 #include "./centipede/execution_metadata.h"
 #include "./centipede/knobs.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./common/defs.h"
 
 namespace fuzztest::internal {
@@ -33,6 +33,7 @@
 
 using ::testing::AllOf;
 using ::testing::Each;
+using ::testing::Field;
 using ::testing::IsSupersetOf;
 using ::testing::Le;
 using ::testing::SizeIs;
@@ -49,10 +50,10 @@
     std::vector<MutationInputRef> mutation_inputs = {{data}};
     constexpr size_t kMutantSequenceLength = 100;
     for (size_t iter = 0; iter < kMutantSequenceLength; iter++) {
-      const std::vector<ByteArray> mutants =
+      const std::vector<Mutant> mutants =
           mutator[i].MutateMany(mutation_inputs, 1);
       ASSERT_EQ(mutants.size(), 1);
-      res[i].push_back(mutants[0]);
+      res[i].push_back(mutants[0].data);
     }
   }
   EXPECT_NE(res[0], res[1]);
@@ -64,7 +65,7 @@
   FuzzTestMutator mutator(knobs, /*seed=*/1);
   EXPECT_TRUE(mutator.set_max_len(kMaxLen));
   constexpr size_t kNumMutantsToGenerate = 10000;
-  const std::vector<ByteArray> mutants = mutator.MutateMany(
+  const std::vector<Mutant> mutants = mutator.MutateMany(
       {
           {/*data=*/{0, 1, 2, 3, 4, 5, 6, 7}},
           {/*data=*/{0}},
@@ -74,70 +75,70 @@
       },
       kNumMutantsToGenerate);
 
-  EXPECT_THAT(mutants,
-              AllOf(SizeIs(kNumMutantsToGenerate), Each(SizeIs(Le(kMaxLen)))));
+  EXPECT_THAT(mutants, AllOf(SizeIs(kNumMutantsToGenerate),
+                             Each(Field(&Mutant::data, SizeIs(Le(kMaxLen))))));
 }
 
 TEST(FuzzTestMutator, CrossOverInsertsDataFromOtherInputs) {
   const Knobs knobs;
   FuzzTestMutator mutator(knobs, /*seed=*/1);
   constexpr size_t kNumMutantsToGenerate = 100000;
-  const std::vector<ByteArray> mutants = mutator.MutateMany(
+  const std::vector<Mutant> mutants = mutator.MutateMany(
       {
           {/*data=*/{0, 1, 2, 3}},
           {/*data=*/{4, 5, 6, 7}},
       },
       kNumMutantsToGenerate);
 
-  EXPECT_THAT(mutants, IsSupersetOf(std::vector<ByteArray>{
-                           // The entire other input
-                           {4, 5, 6, 7, 0, 1, 2, 3},
-                           {0, 1, 4, 5, 6, 7, 2, 3},
-                           {0, 1, 2, 3, 4, 5, 6, 7},
-                           // The prefix of other input
-                           {4, 5, 6, 0, 1, 2, 3},
-                           {0, 1, 4, 5, 6, 2, 3},
-                           {0, 1, 2, 3, 4, 5, 6},
-                           // The suffix of other input
-                           {5, 6, 7, 0, 1, 2, 3},
-                           {0, 1, 5, 6, 7, 2, 3},
-                           {0, 1, 2, 3, 5, 6, 7},
-                           // The middle of other input
-                           {5, 6, 0, 1, 2, 3},
-                           {0, 1, 5, 6, 2, 3},
-                           {0, 1, 2, 3, 5, 6},
-                       }));
+  EXPECT_THAT(GetDataFromMutants(mutants), IsSupersetOf(std::vector<ByteArray>{
+                                               // The entire other input
+                                               {4, 5, 6, 7, 0, 1, 2, 3},
+                                               {0, 1, 4, 5, 6, 7, 2, 3},
+                                               {0, 1, 2, 3, 4, 5, 6, 7},
+                                               // The prefix of other input
+                                               {4, 5, 6, 0, 1, 2, 3},
+                                               {0, 1, 4, 5, 6, 2, 3},
+                                               {0, 1, 2, 3, 4, 5, 6},
+                                               // The suffix of other input
+                                               {5, 6, 7, 0, 1, 2, 3},
+                                               {0, 1, 5, 6, 7, 2, 3},
+                                               {0, 1, 2, 3, 5, 6, 7},
+                                               // The middle of other input
+                                               {5, 6, 0, 1, 2, 3},
+                                               {0, 1, 5, 6, 2, 3},
+                                               {0, 1, 2, 3, 5, 6},
+                                           }));
 }
 
 TEST(FuzzTestMutator, CrossOverOverwritesDataFromOtherInputs) {
   const Knobs knobs;
   FuzzTestMutator mutator(knobs, /*seed=*/1);
   constexpr size_t kNumMutantsToGenerate = 100000;
-  const std::vector<ByteArray> mutants = mutator.MutateMany(
+  const std::vector<Mutant> mutants = mutator.MutateMany(
       {
           {/*data=*/{0, 1, 2, 3, 4, 5, 6, 7}},
           {/*data=*/{100, 101, 102, 103}},
       },
       kNumMutantsToGenerate);
 
-  EXPECT_THAT(mutants, IsSupersetOf(std::vector<ByteArray>{
-                           // The entire other input
-                           {100, 101, 102, 103, 4, 5, 6, 7},
-                           {0, 1, 100, 101, 102, 103, 6, 7},
-                           {0, 1, 2, 3, 100, 101, 102, 103},
-                           // The prefix of other input
-                           {100, 101, 102, 3, 4, 5, 6, 7},
-                           {0, 1, 2, 100, 101, 102, 6, 7},
-                           {0, 1, 2, 3, 4, 100, 101, 102},
-                           // The suffix of other input
-                           {101, 102, 103, 3, 4, 5, 6, 7},
-                           {0, 1, 2, 101, 102, 103, 6, 7},
-                           {0, 1, 2, 3, 4, 101, 102, 103},
-                           // The middle of other input
-                           {101, 102, 2, 3, 4, 5, 6, 7},
-                           {0, 1, 2, 101, 102, 5, 6, 7},
-                           {0, 1, 2, 3, 4, 5, 101, 102},
-                       }));
+  EXPECT_THAT(GetDataFromMutants(mutants), IsSupersetOf(std::vector<ByteArray>{
+                                               // The entire other input
+                                               {100, 101, 102, 103, 4, 5, 6, 7},
+                                               {0, 1, 100, 101, 102, 103, 6, 7},
+                                               {0, 1, 2, 3, 100, 101, 102, 103},
+                                               // The prefix of other input
+                                               {100, 101, 102, 3, 4, 5, 6, 7},
+                                               {0, 1, 2, 100, 101, 102, 6, 7},
+                                               {0, 1, 2, 3, 4, 100, 101, 102},
+                                               // The suffix of other input
+                                               {101, 102, 103, 3, 4, 5, 6, 7},
+                                               {0, 1, 2, 101, 102, 103, 6, 7},
+                                               {0, 1, 2, 3, 4, 101, 102, 103},
+                                               // The middle of other input
+                                               {101, 102, 2, 3, 4, 5, 6, 7},
+                                               {0, 1, 2, 101, 102, 5, 6, 7},
+                                               {0, 1, 2, 3, 4, 5, 101, 102},
+                                           }));
 }
 
 // Test parameter containing the mutation settings and the expectations of a
@@ -181,12 +182,12 @@
   const std::vector<MutationInputRef> inputs = {
       {/*data=*/GetParam().seed_input, /*metadata=*/&metadata}};
   for (size_t i = 0; i < GetParam().max_num_iterations; i++) {
-    const std::vector<ByteArray> mutants = mutator.MutateMany(inputs, 1);
+    const std::vector<Mutant> mutants = mutator.MutateMany(inputs, 1);
     ASSERT_EQ(mutants.size(), 1);
     const auto& mutant = mutants[0];
-    EXPECT_FALSE(unexpected_mutants.contains(mutant))
-        << "Unexpected mutant: {" << absl::StrJoin(mutant, ",") << "}";
-    unmatched_expected_mutants.erase(mutant);
+    EXPECT_FALSE(unexpected_mutants.contains(mutant.data))
+        << "Unexpected mutant: {" << absl::StrJoin(mutant.data, ",") << "}";
+    unmatched_expected_mutants.erase(mutant.data);
     if (unmatched_expected_mutants.empty() &&
         i >= GetParam().min_num_iterations)
       break;
diff --git a/centipede/minimize_crash.cc b/centipede/minimize_crash.cc
index d9fa681..2f41046 100644
--- a/centipede/minimize_crash.cc
+++ b/centipede/minimize_crash.cc
@@ -26,7 +26,7 @@
 #include "absl/synchronization/mutex.h"
 #include "./centipede/centipede_callbacks.h"
 #include "./centipede/environment.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/runner_result.h"
 #include "./centipede/stop.h"
 #include "./centipede/thread_pool.h"
@@ -114,11 +114,11 @@
     // discarding all inputs that are too large.
     // TODO(kcc): modify the Mutate() interface such that max_len can be passed.
     //
-    const std::vector<ByteArray> mutants = callbacks->Mutate(
+    const std::vector<Mutant> mutants = callbacks->Mutate(
         GetMutationInputRefsFromDataInputs(recent_crashers), env.batch_size);
     std::vector<ByteArray> smaller_mutants;
     for (const auto &m : mutants) {
-      if (m.size() < min_known_size) smaller_mutants.push_back(m);
+      if (m.data.size() < min_known_size) smaller_mutants.push_back(m.data);
     }
 
     // Execute all mutants. If a new crasher is found, add it to `queue`.
diff --git a/centipede/mutation_input.h b/centipede/mutation_data.h
similarity index 60%
rename from centipede/mutation_input.h
rename to centipede/mutation_data.h
index 504c753..a4ef7f1 100644
--- a/centipede/mutation_input.h
+++ b/centipede/mutation_data.h
@@ -12,12 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Data types used for mutation inputs.
+// Data types used for mutation.
 //
 // This library is for both engine and runner.
 
-#ifndef THIRD_PARTY_CENTIPEDE_MUTATION_INPUT_H_
-#define THIRD_PARTY_CENTIPEDE_MUTATION_INPUT_H_
+#ifndef THIRD_PARTY_CENTIPEDE_MUTATION_DATA_H_
+#define THIRD_PARTY_CENTIPEDE_MUTATION_DATA_H_
 
 #include <vector>
 
@@ -48,6 +48,39 @@
   return results;
 }
 
+// Represents a mutation result.
+struct Mutant {
+  // The mutant `data`.
+  ByteArray data;
+};
+
+inline bool operator==(const Mutant& mutant, const Mutant& other) {
+  return mutant.data == other.data;
+}
+
+// A reference counterpart of `Mutant`. Needed because it can be constructed
+// from std::string and/or by the C-only dispatcher without copying the
+// underlying data.
+struct MutantRef {
+  MutantRef() = default;
+
+  explicit MutantRef(const Mutant& mutant) : data(mutant.data) {}
+
+  explicit MutantRef(ByteSpan data) : data(data) {}
+
+  ByteSpan data;
+};
+
+inline std::vector<ByteArray> GetDataFromMutants(
+    const std::vector<Mutant>& mutants) {
+  std::vector<ByteArray> results;
+  results.reserve(mutants.size());
+  for (const auto& mutant : mutants) {
+    results.push_back(mutant.data);
+  }
+  return results;
+}
+
 }  // namespace fuzztest::internal
 
-#endif  // THIRD_PARTY_CENTIPEDE_MUTATION_INPUT_H_
+#endif  // THIRD_PARTY_CENTIPEDE_MUTATION_DATA_H_
diff --git a/centipede/mutation_input_test.cc b/centipede/mutation_data_test.cc
similarity index 96%
rename from centipede/mutation_input_test.cc
rename to centipede/mutation_data_test.cc
index f1bc741..4ccf7ca 100644
--- a/centipede/mutation_input_test.cc
+++ b/centipede/mutation_data_test.cc
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 
 #include <vector>
 
diff --git a/centipede/runner.cc b/centipede/runner.cc
index fbf5a2b..4324529 100644
--- a/centipede/runner.cc
+++ b/centipede/runner.cc
@@ -53,7 +53,7 @@
 #include "./centipede/dispatcher_flag_helper.h"
 #include "./centipede/execution_metadata.h"
 #include "./centipede/feature.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/runner_interface.h"
 #include "./centipede/runner_request.h"
 #include "./centipede/runner_result.h"
@@ -327,8 +327,8 @@
 std::string RunnerCallbacks::GetSerializedTargetConfig() { return ""; }
 
 bool RunnerCallbacks::Mutate(
-    const std::vector<MutationInputRef> & /*inputs*/, size_t /*num_mutants*/,
-    std::function<void(ByteSpan)> /*new_mutant_callback*/) {
+    const std::vector<MutationInputRef>& /*inputs*/, size_t /*num_mutants*/,
+    std::function<void(MutantRef)> /*new_mutant_callback*/) {
   RunnerCheck(!HasCustomMutator(),
               "Class deriving from RunnerCallbacks must implement Mutate() if "
               "HasCustomMutator() returns true.");
@@ -358,8 +358,8 @@
     return custom_mutator_cb_ != nullptr;
   }
 
-  bool Mutate(const std::vector<MutationInputRef> &inputs, size_t num_mutants,
-              std::function<void(ByteSpan)> new_mutant_callback) override;
+  bool Mutate(const std::vector<MutationInputRef>& inputs, size_t num_mutants,
+              std::function<void(MutantRef)> new_mutant_callback) override;
 
  private:
   FuzzerTestOneInputCallback test_one_input_cb_;
@@ -623,7 +623,7 @@
   }
   if (!callbacks.HasCustomMutator()) return EXIT_SUCCESS;
 
-  if (!callbacks.Mutate(input_refs, num_mutants, [&](ByteSpan mutant) {
+  if (!callbacks.Mutate(input_refs, num_mutants, [&](MutantRef mutant) {
         MutationResult::WriteMutant(mutant, outputs_blobseq);
       })) {
     return EXIT_FAILURE;
@@ -632,36 +632,41 @@
 }
 
 bool LegacyRunnerCallbacks::Mutate(
-    const std::vector<MutationInputRef> &inputs, size_t num_mutants,
-    std::function<void(ByteSpan)> new_mutant_callback) {
+    const std::vector<MutationInputRef>& inputs, size_t num_mutants,
+    std::function<void(MutantRef)> new_mutant_callback) {
   if (custom_mutator_cb_ == nullptr) return false;
   unsigned int seed = GetRandomSeed();
   const size_t num_inputs = inputs.size();
   const size_t max_mutant_size = state->run_time_flags.max_len;
   constexpr size_t kAverageMutationAttempts = 2;
-  ByteArray mutant(max_mutant_size);
+  // Reused across iterations to save memory allocations.
+  Mutant mutant;
   for (size_t attempt = 0, num_outputs = 0;
        attempt < num_mutants * kAverageMutationAttempts &&
        num_outputs < num_mutants;
        ++attempt) {
-    const auto &input_data = inputs[rand_r(&seed) % num_inputs].data;
-
+    const auto& input_data = inputs[rand_r(&seed) % num_inputs].data;
     size_t size = std::min(input_data.size(), max_mutant_size);
-    std::copy(input_data.cbegin(), input_data.cbegin() + size, mutant.begin());
+    mutant.data.resize(max_mutant_size);
+    std::copy(input_data.cbegin(), input_data.cbegin() + size,
+              mutant.data.begin());
     size_t new_size = 0;
     if ((custom_crossover_cb_ != nullptr) &&
         rand_r(&seed) % 100 < state->run_time_flags.crossover_level) {
       // Perform crossover `crossover_level`% of the time.
       const auto &other_data = inputs[rand_r(&seed) % num_inputs].data;
-      new_size = custom_crossover_cb_(
-          input_data.data(), input_data.size(), other_data.data(),
-          other_data.size(), mutant.data(), max_mutant_size, rand_r(&seed));
+      new_size = custom_crossover_cb_(input_data.data(), input_data.size(),
+                                      other_data.data(), other_data.size(),
+                                      mutant.data.data(), max_mutant_size,
+                                      rand_r(&seed));
     } else {
-      new_size = custom_mutator_cb_(mutant.data(), size, max_mutant_size,
+      new_size = custom_mutator_cb_(mutant.data.data(), size, max_mutant_size,
                                     rand_r(&seed));
     }
     if (new_size == 0) continue;
-    new_mutant_callback({mutant.data(), new_size});
+    if (new_size > max_mutant_size) new_size = max_mutant_size;
+    mutant.data.resize(new_size);
+    new_mutant_callback(MutantRef{mutant});
     ++num_outputs;
   }
   return true;
diff --git a/centipede/runner_interface.h b/centipede/runner_interface.h
index 09e2bbd..cfbf849 100644
--- a/centipede/runner_interface.h
+++ b/centipede/runner_interface.h
@@ -25,7 +25,7 @@
 #include <vector>
 
 #include "absl/base/nullability.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./common/defs.h"
 
 // Typedefs for the libFuzzer API, https://llvm.org/docs/LibFuzzer.html
@@ -153,9 +153,9 @@
   //
   // TODO(xinhaoyuan): Consider supporting only_shrink to speed up
   // input shrinking.
-  virtual bool Mutate(const std::vector<MutationInputRef> &inputs,
+  virtual bool Mutate(const std::vector<MutationInputRef>& inputs,
                       size_t num_mutants,
-                      std::function<void(ByteSpan)> new_mutant_callback);
+                      std::function<void(MutantRef)> new_mutant_callback);
   virtual ~RunnerCallbacks() = default;
 };
 
diff --git a/centipede/runner_request.cc b/centipede/runner_request.cc
index 7d286d3..6a33cb7 100644
--- a/centipede/runner_request.cc
+++ b/centipede/runner_request.cc
@@ -18,7 +18,7 @@
 #include <vector>
 
 #include "./centipede/execution_metadata.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/shared_memory_blob_sequence.h"
 #include "./common/defs.h"
 
diff --git a/centipede/runner_request.h b/centipede/runner_request.h
index 63b90a2..f95189f 100644
--- a/centipede/runner_request.h
+++ b/centipede/runner_request.h
@@ -21,7 +21,7 @@
 #include <vector>
 
 #include "./centipede/execution_metadata.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/shared_memory_blob_sequence.h"
 #include "./common/defs.h"
 
diff --git a/centipede/runner_result.cc b/centipede/runner_result.cc
index 3b726e7..7760fa5 100644
--- a/centipede/runner_result.cc
+++ b/centipede/runner_result.cc
@@ -22,6 +22,7 @@
 
 #include "./centipede/execution_metadata.h"
 #include "./centipede/feature.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/shared_memory_blob_sequence.h"
 #include "./common/defs.h"
 
@@ -185,8 +186,8 @@
        reinterpret_cast<const uint8_t *>(&has_custom_mutator)});
 }
 
-bool MutationResult::WriteMutant(ByteSpan mutant, BlobSequence &blobseq) {
-  return blobseq.Write({kTagMutant, mutant.size(), mutant.data()});
+bool MutationResult::WriteMutant(MutantRef mutant, BlobSequence& blobseq) {
+  return blobseq.Write({kTagMutant, mutant.data.size(), mutant.data.data()});
 }
 
 bool MutationResult::Read(size_t num_mutants, BlobSequence &blobseq) {
@@ -202,7 +203,7 @@
     const Blob blob = blobseq.Read();
     if (blob.tag != kTagMutant) return false;
     if (blob.size == 0) break;
-    mutants_.emplace_back(blob.data, blob.data + blob.size);
+    mutants_.push_back({ByteArray{blob.data, blob.data + blob.size}});
   }
   return true;
 }
diff --git a/centipede/runner_result.h b/centipede/runner_result.h
index 1b94f31..8266103 100644
--- a/centipede/runner_result.h
+++ b/centipede/runner_result.h
@@ -25,6 +25,7 @@
 
 #include "./centipede/execution_metadata.h"
 #include "./centipede/feature.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/shared_memory_blob_sequence.h"
 #include "./common/defs.h"
 
@@ -217,7 +218,7 @@
                                     BlobSequence& blobseq);
 
   // Writes one mutant to `blobseq`. Returns true iff successful.
-  static bool WriteMutant(ByteSpan mutant, BlobSequence& blobseq);
+  static bool WriteMutant(MutantRef mutant, BlobSequence& blobseq);
 
   // Reads whether the target has a custom mutator, and if so, reads at most
   // `num_mutants` mutants from `blobseq`. Returns true iff successful.
@@ -227,13 +228,13 @@
   int exit_code() const { return exit_code_; }
   int& exit_code() { return exit_code_; }
   bool has_custom_mutator() const { return has_custom_mutator_; }
-  const std::vector<ByteArray>& mutants() const& { return mutants_; }
-  std::vector<ByteArray>&& mutants() && { return std::move(mutants_); }
+  const std::vector<Mutant>& mutants() const& { return mutants_; }
+  std::vector<Mutant>&& mutants() && { return std::move(mutants_); }
 
  private:
   int exit_code_ = EXIT_SUCCESS;
   bool has_custom_mutator_ = false;
-  std::vector<ByteArray> mutants_;
+  std::vector<Mutant> mutants_;
 };
 
 }  // namespace fuzztest::internal
diff --git a/centipede/runner_result_test.cc b/centipede/runner_result_test.cc
index 864401a..5ac46f3 100644
--- a/centipede/runner_result_test.cc
+++ b/centipede/runner_result_test.cc
@@ -29,6 +29,7 @@
 #include "gtest/gtest.h"
 #include "./centipede/execution_metadata.h"
 #include "./centipede/feature.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/shared_memory_blob_sequence.h"
 #include "./common/defs.h"
 #include "./common/test_util.h"
@@ -213,9 +214,9 @@
 
   // Write a mutation result.
   ASSERT_TRUE(MutationResult::WriteHasCustomMutator(true, blobseq));
-  ASSERT_TRUE(MutationResult::WriteMutant({1, 2, 3}, blobseq));
-  ASSERT_TRUE(MutationResult::WriteMutant({4, 5, 6}, blobseq));
-  ASSERT_TRUE(MutationResult::WriteMutant({7, 8, 9}, blobseq));
+  ASSERT_TRUE(MutationResult::WriteMutant(MutantRef{{1, 2, 3}}, blobseq));
+  ASSERT_TRUE(MutationResult::WriteMutant(MutantRef{{4, 5, 6}}, blobseq));
+  ASSERT_TRUE(MutationResult::WriteMutant(MutantRef{{7, 8, 9}}, blobseq));
   blobseq.Reset();
 
   MutationResult mutation_result;
@@ -224,7 +225,7 @@
   EXPECT_TRUE(mutation_result.has_custom_mutator());
   EXPECT_THAT(
       mutation_result.mutants(),
-      ElementsAre(ByteArray{1, 2, 3}, ByteArray{4, 5, 6}, ByteArray{7, 8, 9}));
+      ElementsAre(Mutant{{1, 2, 3}}, Mutant{{4, 5, 6}}, Mutant{{7, 8, 9}}));
 }
 
 TEST(ExecutionResult, ReadResultSucceedsOnlyWithInputBegin) {
diff --git a/centipede/test_coverage_util.h b/centipede/test_coverage_util.h
index c06a9bf..4a61ab3 100644
--- a/centipede/test_coverage_util.h
+++ b/centipede/test_coverage_util.h
@@ -25,7 +25,7 @@
 #include "./centipede/corpus.h"
 #include "./centipede/environment.h"
 #include "./centipede/feature.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/runner_result.h"
 #include "./common/defs.h"
 namespace fuzztest::internal {
@@ -50,8 +50,8 @@
     FUZZTEST_CHECK_EQ(EXIT_SUCCESS, result);
     return true;
   }
-  std::vector<ByteArray> Mutate(const std::vector<MutationInputRef> &inputs,
-                                size_t num_mutants) override {
+  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+                             size_t num_mutants) override {
     return {};
   }
 };
diff --git a/centipede/testing/BUILD b/centipede/testing/BUILD
index e127773..216d83e 100644
--- a/centipede/testing/BUILD
+++ b/centipede/testing/BUILD
@@ -85,7 +85,7 @@
         "@abseil-cpp//absl/flags:flag",
         "@abseil-cpp//absl/flags:parse",
         "@com_google_fuzztest//centipede:centipede_runner_no_main",
-        "@com_google_fuzztest//centipede:mutation_input",
+        "@com_google_fuzztest//centipede:mutation_data",
         "@com_google_fuzztest//common:defs",
     ],
 )
diff --git a/centipede/testing/async_failing_target.cc b/centipede/testing/async_failing_target.cc
index e66ac8b..3d81ebc 100644
--- a/centipede/testing/async_failing_target.cc
+++ b/centipede/testing/async_failing_target.cc
@@ -33,7 +33,7 @@
 
   bool Mutate(const std::vector<fuzztest::internal::MutationInputRef>& inputs,
               size_t num_mutants,
-              std::function<void(fuzztest::internal::ByteSpan)>
+              std::function<void(fuzztest::internal::MutantRef)>
                   new_mutant_callback) override {
     if (to_fail_in_mutation) {
       fprintf(stderr, "Fail in mutation\n");
diff --git a/centipede/testing/fuzz_target_with_custom_mutator.cc b/centipede/testing/fuzz_target_with_custom_mutator.cc
index a95dbf8..980ab99 100644
--- a/centipede/testing/fuzz_target_with_custom_mutator.cc
+++ b/centipede/testing/fuzz_target_with_custom_mutator.cc
@@ -19,7 +19,7 @@
 #include "absl/base/nullability.h"
 #include "absl/flags/flag.h"
 #include "absl/flags/parse.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/runner_interface.h"
 #include "./common/defs.h"
 
@@ -28,6 +28,7 @@
           "failure.");
 
 using fuzztest::internal::ByteSpan;
+using fuzztest::internal::MutantRef;
 
 class CustomMutatorRunnerCallbacks
     : public fuzztest::internal::RunnerCallbacks {
@@ -38,12 +39,10 @@
 
   bool Mutate(const std::vector<fuzztest::internal::MutationInputRef>& inputs,
               size_t num_mutants,
-              std::function<void(ByteSpan)> new_mutant_callback) override {
-    size_t i = 0;
-    for (fuzztest::internal::MutationInputRef input : inputs) {
-      if (i++ >= num_mutants) break;
+              std::function<void(MutantRef)> new_mutant_callback) override {
+    for (size_t i = 0; i < inputs.size() && i < num_mutants; ++i) {
       // Just return the original input as a mutant.
-      new_mutant_callback(input.data);
+      new_mutant_callback(MutantRef{inputs[i].data});
     }
     return true;
   }
diff --git a/fuzztest/internal/BUILD b/fuzztest/internal/BUILD
index 0ea46b8..c5d988a 100644
--- a/fuzztest/internal/BUILD
+++ b/fuzztest/internal/BUILD
@@ -79,7 +79,7 @@
         "@com_google_fuzztest//centipede:environment",
         "@com_google_fuzztest//centipede:execution_metadata",
         "@com_google_fuzztest//centipede:fuzztest_mutator",
-        "@com_google_fuzztest//centipede:mutation_input",
+        "@com_google_fuzztest//centipede:mutation_data",
         "@com_google_fuzztest//centipede:runner_result",
         "@com_google_fuzztest//centipede:stop",
         "@com_google_fuzztest//centipede:workdir",
diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc
index 9bb0256..b323e2f 100644
--- a/fuzztest/internal/centipede_adaptor.cc
+++ b/fuzztest/internal/centipede_adaptor.cc
@@ -70,7 +70,7 @@
 #include "./centipede/environment.h"
 #include "./centipede/execution_metadata.h"
 #include "./centipede/fuzztest_mutator.h"
-#include "./centipede/mutation_input.h"
+#include "./centipede/mutation_data.h"
 #include "./centipede/runner_interface.h"
 #include "./centipede/runner_result.h"
 #include "./centipede/stop.h"
@@ -516,10 +516,8 @@
 
   bool HasCustomMutator() const override { 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 {
+  bool Mutate(const std::vector<MutationInputRef>& inputs, size_t num_mutants,
+              std::function<void(MutantRef)> new_mutant_callback) override {
     if (inputs.empty()) return false;
     cmp_tables.resize(inputs.size());
     absl::Cleanup cmp_tables_cleaner = [this]() { cmp_tables.clear(); };
@@ -556,7 +554,7 @@
             fuzzer_impl_.params_domain_.SerializeCorpus(mutant.args));
       }
       new_mutant_callback(
-          {(unsigned char*)mutant_data.data(), mutant_data.size()});
+          MutantRef{{(unsigned char*)mutant_data.data(), mutant_data.size()}});
     }
     return true;
   }