Change a few functions that take const {ByteArray,string}& to take ByteSpan.

This is to avoid copying the underlying data when converting data types for those functions.
E.g. there will be no need to copy data from a vector of `Mutant` to execute them.
Another example is that RemoteFileAppend(std::string) used to copy the data to a ByteArray, but not needed after.

Also changes dependent functions that take const vector<{ByteArray,string}>& to take span<const ByteSpan>, and const vector<MutateInputRef>& -> span<const MutateInputRef> following the same spirit.

PiperOrigin-RevId: 877433395
diff --git a/centipede/BUILD b/centipede/BUILD
index 15fc743..f634d7e 100644
--- a/centipede/BUILD
+++ b/centipede/BUILD
@@ -459,6 +459,7 @@
         ":shared_memory_blob_sequence",
         ":execution_metadata",
         ":mutation_data",
+        "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//common:defs",
     ],
 )
@@ -486,6 +487,7 @@
         ":knobs",
         ":mutation_data",
         "@abseil-cpp//absl/base:nullability",
+        "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//common:defs",
     ],
 )
@@ -642,6 +644,7 @@
         "@abseil-cpp//absl/strings",
         "@abseil-cpp//absl/synchronization",
         "@abseil-cpp//absl/time",
+        "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//common:blob_file",
         "@com_google_fuzztest//common:defs",
         "@com_google_fuzztest//common:hash",
@@ -738,6 +741,7 @@
         "@abseil-cpp//absl/strings:str_format",
         "@abseil-cpp//absl/synchronization",
         "@abseil-cpp//absl/time",
+        "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//common:blob_file",
         "@com_google_fuzztest//common:defs",
         "@com_google_fuzztest//common:hash",
@@ -861,6 +865,7 @@
         ":stop",
         "@abseil-cpp//absl/status",
         "@abseil-cpp//absl/status:statusor",
+        "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//common:defs",
         "@com_google_fuzztest//common:logging",
     ],
@@ -1102,6 +1107,7 @@
         ":centipede_runner_no_main",
         ":mutation_data",
         "@abseil-cpp//absl/base:nullability",
+        "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//common:defs",
     ],
 )
@@ -1242,6 +1248,7 @@
         ":mutation_data",
         ":runner_result",
         ":util",
+        "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//common:defs",
         "@com_google_fuzztest//common:logging",
     ],
@@ -1281,6 +1288,7 @@
         ":centipede_callbacks",
         ":environment",
         ":runner_result",
+        "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//common:defs",
         "@googletest//:gtest_main",
     ],
@@ -1458,6 +1466,7 @@
         ":util",
         ":workdir",
         "@abseil-cpp//absl/base:nullability",
+        "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//common:defs",
         "@com_google_fuzztest//common:test_util",
         "@googletest//:gtest_main",
@@ -1875,6 +1884,7 @@
         "@abseil-cpp//absl/container:flat_hash_map",
         "@abseil-cpp//absl/strings:str_format",
         "@abseil-cpp//absl/time",
+        "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//common:defs",
         "@com_google_fuzztest//common:hash",
         "@com_google_fuzztest//common:temp_dir",
@@ -1925,6 +1935,7 @@
         "@abseil-cpp//absl/container:flat_hash_set",
         "@abseil-cpp//absl/strings",
         "@abseil-cpp//absl/time",
+        "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//common:defs",
         "@com_google_fuzztest//common:hash",
         "@com_google_fuzztest//common:logging",
diff --git a/centipede/byte_array_mutator.cc b/centipede/byte_array_mutator.cc
index 60834c7..f117eea 100644
--- a/centipede/byte_array_mutator.cc
+++ b/centipede/byte_array_mutator.cc
@@ -22,6 +22,7 @@
 #include <utility>
 #include <vector>
 
+#include "absl/types/span.h"
 #include "./centipede/execution_metadata.h"
 #include "./centipede/knobs.h"
 #include "./centipede/mutation_data.h"
@@ -322,7 +323,7 @@
 const KnobId knob_mutate_or_crossover = Knobs::NewId("mutate_or_crossover");
 
 std::vector<Mutant> ByteArrayMutator::MutateMany(
-    const std::vector<MutationInputRef>& inputs, size_t num_mutants) {
+    absl::Span<const MutationInputRef> inputs, size_t num_mutants) {
   if (inputs.empty()) abort();
   // TODO(xinhaoyuan): Consider metadata in other inputs instead of always the
   // first one.
diff --git a/centipede/byte_array_mutator.h b/centipede/byte_array_mutator.h
index cec8208..349ed48 100644
--- a/centipede/byte_array_mutator.h
+++ b/centipede/byte_array_mutator.h
@@ -23,6 +23,7 @@
 #include <vector>
 
 #include "absl/base/nullability.h"
+#include "absl/types/span.h"
 #include "./centipede/execution_metadata.h"
 #include "./centipede/knobs.h"
 #include "./centipede/mutation_data.h"
@@ -108,7 +109,7 @@
   }
 
   // Takes non-empty `inputs` and produces `num_mutants` mutants.
-  std::vector<Mutant> MutateMany(const std::vector<MutationInputRef>& inputs,
+  std::vector<Mutant> MutateMany(absl::Span<const MutationInputRef> inputs,
                                  size_t num_mutants);
 
   using CrossOverFn = void (ByteArrayMutator::*)(ByteArray &,
diff --git a/centipede/centipede.cc b/centipede/centipede.cc
index 2dab183..345af69 100644
--- a/centipede/centipede.cc
+++ b/centipede/centipede.cc
@@ -72,6 +72,7 @@
 #include "absl/synchronization/mutex.h"
 #include "absl/time/clock.h"
 #include "absl/time/time.h"
+#include "absl/types/span.h"
 #include "./centipede/binary_info.h"
 #include "./centipede/centipede_callbacks.h"
 #include "./centipede/command.h"
@@ -362,7 +363,7 @@
   }
 }
 
-bool Centipede::InputPassesFilter(const ByteArray &input) {
+bool Centipede::InputPassesFilter(ByteSpan input) {
   if (env_.input_filter.empty()) return true;
   WriteToLocalFile(input_filter_path_, input);
   bool result = input_filter_cmd_.Execute() == EXIT_SUCCESS;
@@ -371,8 +372,8 @@
 }
 
 bool Centipede::ExecuteAndReportCrash(std::string_view binary,
-                                      const std::vector<ByteArray> &input_vec,
-                                      BatchResult &batch_result) {
+                                      absl::Span<const ByteSpan> input_vec,
+                                      BatchResult& batch_result) {
   bool success = user_callbacks_.Execute(binary, input_vec, batch_result);
   if (success) return true;
   if (ShouldStop()) {
@@ -427,7 +428,7 @@
 }
 
 bool Centipede::RunBatch(
-    const std::vector<ByteArray>& input_vec,
+    absl::Span<const ByteSpan> input_vec,
     BlobFileWriter* absl_nullable corpus_file,
     BlobFileWriter* absl_nullable features_file,
     BlobFileWriter* absl_nullable unconditional_features_file) {
@@ -582,11 +583,11 @@
   while (!to_rerun.empty()) {
     if (ShouldStop()) break;
     size_t batch_size = std::min(to_rerun.size(), env_.batch_size);
-    std::vector<ByteArray> batch(to_rerun.end() - batch_size, to_rerun.end());
-    to_rerun.resize(to_rerun.size() - batch_size);
+    std::vector<ByteSpan> batch(to_rerun.end() - batch_size, to_rerun.end());
     if (RunBatch(batch, nullptr, nullptr, features_file.get())) {
       UpdateAndMaybeLogStats("rerun-old", 1);
     }
+    to_rerun.resize(to_rerun.size() - batch_size);
   }
 }
 
@@ -778,7 +779,8 @@
     seed_inputs.push_back({0});
   }
 
-  RunBatch(seed_inputs, corpus_file, features_file,
+  RunBatch(std::vector<ByteSpan>{seed_inputs.begin(), seed_inputs.end()},
+           corpus_file, features_file,
            /*unconditional_features_file=*/nullptr);
   FUZZTEST_LOG(INFO) << "Number of input seeds available: "
                      << num_seeds_available
@@ -873,10 +875,10 @@
     if (ShouldStop()) break;
     new_runs += mutants.size();
 
-    std::vector<ByteArray> inputs;
+    std::vector<ByteSpan> inputs;
     inputs.reserve(mutants.size());
     for (auto& mutant : mutants) {
-      inputs.push_back(std::move(mutant.data));
+      inputs.push_back(mutant.data);
     }
     bool gained_new_coverage =
         RunBatch(inputs, corpus_file.get(), features_file.get(), nullptr);
@@ -922,8 +924,8 @@
 }
 
 void Centipede::ReportCrash(std::string_view binary,
-                            const std::vector<ByteArray> &input_vec,
-                            const BatchResult &batch_result) {
+                            absl::Span<const ByteSpan> input_vec,
+                            const BatchResult& batch_result) {
   FUZZTEST_CHECK_EQ(input_vec.size(), batch_result.results().size());
 
   const size_t suspect_input_idx = std::clamp<size_t>(
@@ -1019,7 +1021,7 @@
       << "Executing inputs one-by-one, trying to find the reproducer";
   for (auto input_idx : input_idxs_to_try) {
     if (ShouldStop()) break;
-    const auto &one_input = input_vec[input_idx];
+    const auto one_input = input_vec[input_idx];
     BatchResult one_input_batch_result;
     if (!user_callbacks_.Execute(binary, {one_input}, one_input_batch_result) &&
         one_input_batch_result.IsInputFailure() &&
diff --git a/centipede/centipede.h b/centipede/centipede.h
index aa25a42..b090b1f 100644
--- a/centipede/centipede.h
+++ b/centipede/centipede.h
@@ -24,6 +24,7 @@
 #include "absl/base/nullability.h"
 #include "absl/status/status.h"
 #include "absl/time/time.h"
+#include "absl/types/span.h"
 #include "./centipede/binary_info.h"
 #include "./centipede/centipede_callbacks.h"
 #include "./centipede/command.h"
@@ -84,7 +85,7 @@
   //   * 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,
+  bool RunBatch(absl::Span<const ByteSpan> input_vec,
                 BlobFileWriter* absl_nullable corpus_file,
                 BlobFileWriter* absl_nullable features_file,
                 BlobFileWriter* absl_nullable unconditional_features_file);
@@ -140,13 +141,13 @@
                                         size_t batch_index);
 
   // Returns true if `input` passes env_.input_filter.
-  bool InputPassesFilter(const ByteArray &input);
+  bool InputPassesFilter(ByteSpan input);
   // Executes `binary` with `input_vec` and `batch_result` as input/output.
   // If the binary crashes, calls ReportCrash().
   // Returns true iff there were no crashes.
   bool ExecuteAndReportCrash(std::string_view binary,
-                             const std::vector<ByteArray> &input_vec,
-                             BatchResult &batch_result);
+                             absl::Span<const ByteSpan> input_vec,
+                             BatchResult& batch_result);
   // Reports a crash and saves the reproducer to workdir/crashes, if possible.
   // `binary` is the binary causing the crash.
   // Prints the first `env_.max_num_crash_reports` logs.
@@ -156,8 +157,8 @@
   // as a hint when choosing which input to try first.
   // Stops early if `EarlyExitRequested()`.
   void ReportCrash(std::string_view binary,
-                   const std::vector<ByteArray> &input_vec,
-                   const BatchResult &batch_result);
+                   absl::Span<const ByteSpan> input_vec,
+                   const BatchResult& batch_result);
   // Merges shard `shard_index_to_merge` of the corpus in `merge_from_dir`
   // into the current corpus.
   // Writes added inputs to the current shard.
diff --git a/centipede/centipede_callbacks.cc b/centipede/centipede_callbacks.cc
index 66c6c6c..c37fa85 100644
--- a/centipede/centipede_callbacks.cc
+++ b/centipede/centipede_callbacks.cc
@@ -45,6 +45,7 @@
 #include "absl/synchronization/mutex.h"
 #include "absl/time/clock.h"
 #include "absl/time/time.h"
+#include "absl/types/span.h"
 #include "./centipede/binary_info.h"
 #include "./centipede/command.h"
 #include "./centipede/control_flow.h"
@@ -534,7 +535,7 @@
 }
 
 int CentipedeCallbacks::ExecuteCentipedeSancovBinaryWithShmem(
-    std::string_view binary, const std::vector<ByteArray>& inputs,
+    std::string_view binary, absl::Span<const ByteSpan> inputs,
     BatchResult& batch_result) {
   auto start_time = absl::Now();
   batch_result.ClearAndResize(inputs.size());
@@ -734,7 +735,7 @@
 
 // See also: MutateInputsFromShmem().
 MutationResult CentipedeCallbacks::MutateViaExternalBinary(
-    std::string_view binary, const std::vector<MutationInputRef>& inputs,
+    std::string_view binary, absl::Span<const MutationInputRef> inputs,
     size_t num_mutants) {
   FUZZTEST_CHECK(!env_.has_input_wildcards)
       << "Standalone binary does not support custom mutator";
diff --git a/centipede/centipede_callbacks.h b/centipede/centipede_callbacks.h
index 181fd2a..a4fb2bb 100644
--- a/centipede/centipede_callbacks.h
+++ b/centipede/centipede_callbacks.h
@@ -25,6 +25,7 @@
 
 #include "absl/base/nullability.h"
 #include "absl/status/statusor.h"
+#include "absl/types/span.h"
 #include "./centipede/binary_info.h"
 #include "./centipede/byte_array_mutator.h"
 #include "./centipede/command.h"
@@ -68,12 +69,12 @@
   // Post-condition:
   // `batch_result` has results for every `input`, even on failure.
   virtual bool Execute(std::string_view binary,
-                       const std::vector<ByteArray> &inputs,
-                       BatchResult &batch_result) = 0;
+                       absl::Span<const ByteSpan> inputs,
+                       BatchResult& batch_result) = 0;
 
   // Takes non-empty `inputs` and returns at most `num_mutants` mutated inputs.
-  virtual std::vector<Mutant> Mutate(
-      const std::vector<MutationInputRef>& inputs, size_t num_mutants) {
+  virtual std::vector<Mutant> Mutate(absl::Span<const 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);
@@ -105,9 +106,9 @@
 
   // Same as ExecuteCentipedeSancovBinary, but uses shared memory.
   // Much faster for fast targets since it uses fewer system calls.
-  int ExecuteCentipedeSancovBinaryWithShmem(
-      std::string_view binary, const std::vector<ByteArray> &inputs,
-      BatchResult &batch_result);
+  int ExecuteCentipedeSancovBinaryWithShmem(std::string_view binary,
+                                            absl::Span<const ByteSpan> inputs,
+                                            BatchResult& batch_result);
 
   // Constructs a string CENTIPEDE_RUNNER_FLAGS=":flag1:flag2:...",
   // where the flags are determined by `env` and also include `extra_flags`.
@@ -151,7 +152,7 @@
   // whether the binary has a custom mutator, and if it does, `mutants` contains
   // at most `num_mutants` non-empty mutants.
   MutationResult MutateViaExternalBinary(
-      std::string_view binary, const std::vector<MutationInputRef> &inputs,
+      std::string_view binary, absl::Span<const MutationInputRef> inputs,
       size_t num_mutants);
 
   // Loads the dictionary from `dictionary_path`,
diff --git a/centipede/centipede_callbacks_test.cc b/centipede/centipede_callbacks_test.cc
index 60fb8fc..ad8c68a 100644
--- a/centipede/centipede_callbacks_test.cc
+++ b/centipede/centipede_callbacks_test.cc
@@ -18,6 +18,7 @@
 #include <vector>
 
 #include "gtest/gtest.h"
+#include "absl/types/span.h"
 #include "./centipede/environment.h"
 #include "./centipede/runner_result.h"
 #include "./common/defs.h"
@@ -28,7 +29,7 @@
 class FakeCallbacks : public CentipedeCallbacks {
  public:
   explicit FakeCallbacks(const Environment& env) : CentipedeCallbacks(env) {}
-  bool Execute(std::string_view binary, const std::vector<ByteArray>& inputs,
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
                BatchResult& batch_result) override {
     return true;
   }
diff --git a/centipede/centipede_default_callbacks.cc b/centipede/centipede_default_callbacks.cc
index 67fe09f..3f07174 100644
--- a/centipede/centipede_default_callbacks.cc
+++ b/centipede/centipede_default_callbacks.cc
@@ -23,6 +23,7 @@
 
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
+#include "absl/types/span.h"
 #include "./centipede/centipede_callbacks.h"
 #include "./centipede/environment.h"
 #include "./centipede/mutation_data.h"
@@ -46,8 +47,8 @@
 }
 
 bool CentipedeDefaultCallbacks::Execute(std::string_view binary,
-                                        const std::vector<ByteArray> &inputs,
-                                        BatchResult &batch_result) {
+                                        absl::Span<const ByteSpan> inputs,
+                                        BatchResult& batch_result) {
   return ExecuteCentipedeSancovBinaryWithShmem(binary, inputs, batch_result) ==
          0;
 }
@@ -73,7 +74,7 @@
 }
 
 std::vector<Mutant> CentipedeDefaultCallbacks::Mutate(
-    const std::vector<MutationInputRef>& inputs, size_t num_mutants) {
+    absl::Span<const 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 9fb157f..02fff74 100644
--- a/centipede/centipede_default_callbacks.h
+++ b/centipede/centipede_default_callbacks.h
@@ -26,6 +26,7 @@
 #include <vector>
 
 #include "absl/status/statusor.h"
+#include "absl/types/span.h"
 #include "./centipede/centipede_callbacks.h"
 #include "./centipede/environment.h"
 #include "./centipede/mutation_data.h"
@@ -40,9 +41,9 @@
   explicit CentipedeDefaultCallbacks(const Environment &env);
   size_t GetSeeds(size_t num_seeds, std::vector<ByteArray> &seeds) override;
   absl::StatusOr<std::string> GetSerializedTargetConfig() override;
-  bool Execute(std::string_view binary, const std::vector<ByteArray> &inputs,
-               BatchResult &batch_result) override;
-  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
+               BatchResult& batch_result) override;
+  std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs,
                              size_t num_mutants) override;
 
  private:
diff --git a/centipede/centipede_interface.cc b/centipede/centipede_interface.cc
index cd5e00d..9031169 100644
--- a/centipede/centipede_interface.cc
+++ b/centipede/centipede_interface.cc
@@ -111,12 +111,9 @@
     }
     ByteSpan blob;
     while (blob_reader->Read(blob) == absl::OkStatus()) {
-      ByteArray bytes;
-      bytes.insert(bytes.begin(), blob.data(), blob.end());
-      // TODO(kcc): [impl] add a variant of WriteToLocalFile that accepts Span.
-      WriteToLocalFile(tmpfile, bytes);
+      WriteToLocalFile(tmpfile, blob);
       std::string command_line = absl::StrReplaceAll(
-          env.for_each_blob, {{"%P", tmpfile}, {"%H", Hash(bytes)}});
+          env.for_each_blob, {{"%P", tmpfile}, {"%H", Hash(blob)}});
       Command cmd(command_line);
       // TODO(kcc): [as-needed] this creates one process per blob.
       // If this flag gets active use, we may want to define special cases,
diff --git a/centipede/centipede_test.cc b/centipede/centipede_test.cc
index 9454d34..a034cc1 100644
--- a/centipede/centipede_test.cc
+++ b/centipede/centipede_test.cc
@@ -33,6 +33,7 @@
 #include "absl/strings/str_cat.h"
 #include "absl/time/clock.h"
 #include "absl/time/time.h"
+#include "absl/types/span.h"
 #include "./centipede/centipede_callbacks.h"
 #include "./centipede/centipede_default_callbacks.h"
 #include "./centipede/centipede_interface.h"
@@ -61,6 +62,13 @@
 using ::testing::Not;
 using ::testing::SizeIs;
 
+inline std::vector<ByteSpan> AsByteSpans(const std::vector<ByteArray>& inputs) {
+  std::vector<ByteSpan> results;
+  results.reserve(inputs.size());
+  for (const auto& input : inputs) results.push_back(input);
+  return results;
+}
+
 // A mock for CentipedeCallbacks.
 class CentipedeMock : public CentipedeCallbacks {
  public:
@@ -68,8 +76,8 @@
   // Doesn't execute anything
   // Sets `batch_result.results()` based on the values of `inputs`:
   // Collects various stats about the inputs, to be checked in tests.
-  bool Execute(std::string_view binary, const std::vector<ByteArray> &inputs,
-               BatchResult &batch_result) override {
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
+               BatchResult& batch_result) override {
     batch_result.results().clear();
     // For every input, we create a 256-element array `counters`, where
     // i-th element is the number of bytes with the value 'i' in the input.
@@ -106,7 +114,7 @@
   // (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<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+  std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs,
                              size_t num_mutants) override {
     std::vector<Mutant> mutants;
     mutants.reserve(num_mutants);
@@ -343,14 +351,14 @@
  public:
   explicit MutateCallbacks(const Environment &env) : CentipedeCallbacks(env) {}
   // Will not be called.
-  bool Execute(std::string_view binary, const std::vector<ByteArray> &inputs,
-               BatchResult &batch_result) override {
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
+               BatchResult& batch_result) override {
     FUZZTEST_LOG(FATAL);
     return false;
   }
 
   // Will not be called.
-  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+  std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs,
                              size_t num_mutants) override {
     FUZZTEST_LOG(FATAL);
   }
@@ -489,8 +497,8 @@
   // Doesn't execute anything.
   // All inputs are 1-byte long.
   // For an input {X}, the feature output is {X}.
-  bool Execute(std::string_view binary, const std::vector<ByteArray> &inputs,
-               BatchResult &batch_result) override {
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
+               BatchResult& batch_result) override {
     batch_result.results().resize(inputs.size());
     for (size_t i = 0, n = inputs.size(); i < n; ++i) {
       FUZZTEST_CHECK_EQ(inputs[i].size(), 1);
@@ -500,7 +508,7 @@
   }
 
   // Every consecutive mutation is {number_of_mutations_} (starting from 1).
-  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+  std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs,
                              size_t num_mutants) override {
     std::vector<Mutant> mutants(num_mutants);
     for (auto &mutant : mutants) {
@@ -578,14 +586,14 @@
   }
 
   // Executes the target in the normal way.
-  bool Execute(std::string_view binary, const std::vector<ByteArray> &inputs,
-               BatchResult &batch_result) override {
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
+               BatchResult& batch_result) override {
     return ExecuteCentipedeSancovBinaryWithShmem(env_.binary, inputs,
                                                  batch_result) == EXIT_SUCCESS;
   }
 
   // Sets the inputs to one of 3 pre-defined values.
-  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+  std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs,
                              size_t num_mutants) override {
     for (auto &input : inputs) {
       if (!seed_inputs_.contains(input.data)) {
@@ -687,8 +695,8 @@
 
   // Doesn't execute anything.
   // On certain combinations of {binary,input} returns false.
-  bool Execute(std::string_view binary, const std::vector<ByteArray> &inputs,
-               BatchResult &batch_result) override {
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
+               BatchResult& batch_result) override {
     bool res = true;
     for (const auto &input : inputs) {
       if (input.size() != 1) continue;
@@ -706,7 +714,7 @@
   }
 
   // Sets the mutants to different 1-byte values.
-  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+  std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs,
                              size_t num_mutants) override {
     std::vector<Mutant> mutants(num_mutants);
     for (auto &mutant : mutants) {
@@ -806,8 +814,8 @@
   // Doesn't execute anything.
   // Crash when 0th char of input to binary b1 equals `crashing_input_idx_`, but
   // only on 1st exec.
-  bool Execute(std::string_view binary, const std::vector<ByteArray> &inputs,
-               BatchResult &batch_result) override {
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
+               BatchResult& batch_result) override {
     batch_result.ClearAndResize(inputs.size());
     bool res = true;
     if (!first_pass_) {
@@ -819,7 +827,7 @@
       if (input[0] == crashing_input_idx_) {
         if (first_pass_) {
           first_pass_ = false;
-          crashing_input_ = input;
+          crashing_input_ = {input.begin(), input.end()};
           // TODO(b/274705740): `num_outputs_read()` is the number of outputs
           //  that Centipede engine *expects* to have been read from *the
           //  current BatchResult* by the *particular* implementation of
@@ -839,7 +847,7 @@
   }
 
   // Sets the mutants to different 1-byte values.
-  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+  std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs,
                              size_t num_mutants) override {
     std::vector<Mutant> mutants;
     mutants.reserve(num_mutants);
@@ -974,7 +982,7 @@
 
   BatchResult batch_result;
   const std::vector<ByteArray> inputs = {{0}};
-  ASSERT_TRUE(callbacks.Execute(env.binary, inputs, batch_result));
+  ASSERT_TRUE(callbacks.Execute(env.binary, AsByteSpans(inputs), batch_result));
   ASSERT_EQ(batch_result.results().size(), 1);
   bool found_startup_cmp_entry = false;
   batch_result.results()[0].metadata().ForEachCmpEntry(
@@ -991,15 +999,15 @@
                                           std::thread::id execute_thread_id)
       : CentipedeCallbacks(env), execute_thread_id_(execute_thread_id) {}
 
-  bool Execute(std::string_view binary, const std::vector<ByteArray> &inputs,
-               BatchResult &batch_result) override {
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
+               BatchResult& batch_result) override {
     batch_result.ClearAndResize(inputs.size());
     thread_check_passed_ = thread_check_passed_ &&
                            std::this_thread::get_id() == execute_thread_id_;
     return true;
   }
 
-  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+  std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs,
                              size_t num_mutants) override {
     return {num_mutants, {/*data=*/{0}}};
   }
@@ -1036,7 +1044,8 @@
   BatchResult batch_result;
   const std::vector<ByteArray> inputs = {ByteArray{'s', 't', 'k'}};
 
-  ASSERT_FALSE(callbacks.Execute(env.binary, inputs, batch_result));
+  ASSERT_FALSE(
+      callbacks.Execute(env.binary, AsByteSpans(inputs), batch_result));
   EXPECT_THAT(batch_result.log(), HasSubstr("Stack limit exceeded"));
   EXPECT_EQ(batch_result.failure_description(), "stack-limit-exceeded");
 }
@@ -1045,8 +1054,8 @@
  public:
   using CentipedeCallbacks::CentipedeCallbacks;
 
-  bool Execute(std::string_view binary, const std::vector<ByteArray> &inputs,
-               BatchResult &batch_result) override {
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
+               BatchResult& batch_result) override {
     ++execute_count_;
     batch_result.ClearAndResize(inputs.size());
     batch_result.exit_code() = EXIT_FAILURE;
@@ -1054,7 +1063,7 @@
     return false;
   }
 
-  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+  std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs,
                              size_t num_mutants) override {
     return {num_mutants, {/*data=*/{0}}};
   }
@@ -1082,8 +1091,8 @@
  public:
   using CentipedeCallbacks::CentipedeCallbacks;
 
-  bool Execute(std::string_view binary, const std::vector<ByteArray> &inputs,
-               BatchResult &batch_result) override {
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
+               BatchResult& batch_result) override {
     ++execute_count_;
     batch_result.ClearAndResize(inputs.size());
     batch_result.exit_code() = EXIT_FAILURE;
@@ -1092,7 +1101,7 @@
     return false;
   }
 
-  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+  std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs,
                              size_t num_mutants) override {
     return {num_mutants, {/*data=*/{0}}};
   }
@@ -1120,8 +1129,8 @@
  public:
   using CentipedeCallbacks::CentipedeCallbacks;
 
-  bool Execute(std::string_view binary, const std::vector<ByteArray> &inputs,
-               BatchResult &batch_result) override {
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
+               BatchResult& batch_result) override {
     ++execute_count_;
     batch_result.ClearAndResize(inputs.size());
     batch_result.exit_code() = EXIT_FAILURE;
@@ -1130,7 +1139,7 @@
     return false;
   }
 
-  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+  std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs,
                              size_t num_mutants) override {
     return {num_mutants, {/*data=*/{0}}};
   }
@@ -1230,7 +1239,8 @@
   env.fork_server = false;
 
   // Test that the process does not get stuck and exits promptly.
-  EXPECT_FALSE(callbacks.Execute(env.binary, {{0}}, batch_result));
+  const ByteArray input = {0};
+  EXPECT_FALSE(callbacks.Execute(env.binary, {input}, batch_result));
 }
 
 TEST_F(CentipedeWithTemporaryLocalDir, ExecuteEndsAfterCustomFailure) {
@@ -1242,11 +1252,11 @@
       {'c', 'u', 's', 't', 'o', 'm'},
       {'c', 'u', 's', 't', 'o', 'm'},
   };
-  EXPECT_FALSE(callbacks.Execute(env.binary, inputs, result));
+  EXPECT_FALSE(callbacks.Execute(env.binary, AsByteSpans(inputs), result));
   EXPECT_THAT(result.failure_description(), HasSubstr("custom 0"));
   EXPECT_THAT(result.log(), AllOf(HasSubstr("custom failure 0"),
                                   Not(HasSubstr("custom failure 1"))));
-  EXPECT_FALSE(callbacks.Execute(env.binary, inputs, result));
+  EXPECT_FALSE(callbacks.Execute(env.binary, AsByteSpans(inputs), result));
   EXPECT_THAT(result.failure_description(), HasSubstr("custom 1"));
   EXPECT_THAT(result.log(), AllOf(HasSubstr("custom failure 1"),
                                   Not(HasSubstr("custom failure 2"))));
@@ -1262,7 +1272,7 @@
       {'s', 'o', 'm', 'e'},
   };
   ClearEarlyStopRequestAndSetStopTime(absl::InfiniteFuture());
-  EXPECT_TRUE(callbacks.Execute(env.binary, inputs, result));
+  EXPECT_TRUE(callbacks.Execute(env.binary, AsByteSpans(inputs), result));
   // Match the error log to check for retrying mutation.
   EXPECT_DEATH(
       [&] {
diff --git a/centipede/corpus.cc b/centipede/corpus.cc
index f3bc001..def1496 100644
--- a/centipede/corpus.cc
+++ b/centipede/corpus.cc
@@ -229,7 +229,7 @@
   return subset_to_remove.size();
 }
 
-void Corpus::Add(const ByteArray& data, const FeatureVec& fv,
+void Corpus::Add(ByteSpan data, const FeatureVec& fv,
                  const ExecutionMetadata& metadata,
                  const ExecutionResult::Stats& stats, const FeatureSet& fs,
                  const CoverageFrontier& coverage_frontier) {
@@ -237,7 +237,7 @@
   FUZZTEST_CHECK(!data.empty())
       << "Got request to add empty element to corpus: ignoring";
   FUZZTEST_CHECK_EQ(records_.size(), weighted_distribution_.size());
-  records_.push_back({data, fv, metadata, stats});
+  records_.push_back({{data.begin(), data.end()}, fv, metadata, stats});
   // Will be updated by `UpdateWeights`.
   weighted_distribution_.AddWeight(0);
 }
diff --git a/centipede/corpus.h b/centipede/corpus.h
index 1a1c4c1..72ef3ce 100644
--- a/centipede/corpus.h
+++ b/centipede/corpus.h
@@ -121,7 +121,7 @@
   // Adds a corpus element, consisting of 'data' (the input bytes, non-empty),
   // 'fv' (the features associated with this input), and execution `metadata`.
   // `fs` is used to compute weights of `fv`.
-  void Add(const ByteArray& data, const FeatureVec& fv,
+  void Add(ByteSpan data, const FeatureVec& fv,
            const ExecutionMetadata& metadata,
            const ExecutionResult::Stats& stats, const FeatureSet& fs,
            const CoverageFrontier& coverage_frontier);
diff --git a/centipede/crash_deduplication_test.cc b/centipede/crash_deduplication_test.cc
index bd1966e..f878871 100644
--- a/centipede/crash_deduplication_test.cc
+++ b/centipede/crash_deduplication_test.cc
@@ -27,6 +27,7 @@
 #include "absl/strings/str_format.h"
 #include "absl/time/clock.h"
 #include "absl/time/time.h"
+#include "absl/types/span.h"
 #include "./centipede/centipede_callbacks.h"
 #include "./centipede/crash_summary.h"
 #include "./centipede/environment.h"
@@ -168,7 +169,7 @@
       absl::flat_hash_map<std::string, Crash> crashing_inputs)
       : CentipedeCallbacks(env), crashing_inputs_(std::move(crashing_inputs)) {}
 
-  bool Execute(std::string_view binary, const std::vector<ByteArray>& inputs,
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
                BatchResult& batch_result) override {
     batch_result.ClearAndResize(inputs.size());
     for (ByteSpan input : inputs) {
diff --git a/centipede/fuzztest_mutator.cc b/centipede/fuzztest_mutator.cc
index 6985fdf..f46041f 100644
--- a/centipede/fuzztest_mutator.cc
+++ b/centipede/fuzztest_mutator.cc
@@ -140,7 +140,7 @@
 }
 
 std::vector<Mutant> FuzzTestMutator::MutateMany(
-    const std::vector<MutationInputRef>& inputs, size_t num_mutants) {
+    absl::Span<const MutationInputRef> inputs, size_t num_mutants) {
   if (inputs.empty()) abort();
   auto& cmp_tables = mutation_metadata_->cmp_tables;
   cmp_tables.resize(inputs.size());
diff --git a/centipede/fuzztest_mutator.h b/centipede/fuzztest_mutator.h
index ff204f2..8ee29ae 100644
--- a/centipede/fuzztest_mutator.h
+++ b/centipede/fuzztest_mutator.h
@@ -20,6 +20,7 @@
 #include <memory>
 #include <vector>
 
+#include "absl/types/span.h"
 #include "./centipede/execution_metadata.h"
 #include "./centipede/knobs.h"
 #include "./centipede/mutation_data.h"
@@ -44,7 +45,7 @@
   ~FuzzTestMutator();
 
   // Takes non-empty `inputs` and produces `num_mutants` mutants.
-  std::vector<Mutant> MutateMany(const std::vector<MutationInputRef>& inputs,
+  std::vector<Mutant> MutateMany(absl::Span<const MutationInputRef> inputs,
                                  size_t num_mutants);
 
   // Adds `dict_entries` to the internal mutation dictionary.
diff --git a/centipede/minimize_crash.cc b/centipede/minimize_crash.cc
index 2f41046..245846c 100644
--- a/centipede/minimize_crash.cc
+++ b/centipede/minimize_crash.cc
@@ -116,7 +116,7 @@
     //
     const std::vector<Mutant> mutants = callbacks->Mutate(
         GetMutationInputRefsFromDataInputs(recent_crashers), env.batch_size);
-    std::vector<ByteArray> smaller_mutants;
+    std::vector<ByteSpan> smaller_mutants;
     for (const auto &m : mutants) {
       if (m.data.size() < min_known_size) smaller_mutants.push_back(m.data);
     }
@@ -125,10 +125,10 @@
     if (!callbacks->Execute(env.binary, smaller_mutants, batch_result)) {
       size_t crash_inputs_idx = batch_result.num_outputs_read();
       FUZZTEST_CHECK_LT(crash_inputs_idx, smaller_mutants.size());
-      const auto &new_crasher = smaller_mutants[crash_inputs_idx];
+      const auto new_crasher = smaller_mutants[crash_inputs_idx];
       FUZZTEST_LOG(INFO) << "Crasher: size: " << new_crasher.size() << ": "
                          << AsPrintableString(new_crasher, /*max_len=*/40);
-      queue.AddCrasher(new_crasher);
+      queue.AddCrasher({new_crasher.begin(), new_crasher.end()});
     }
   }
 }
diff --git a/centipede/minimize_crash_test.cc b/centipede/minimize_crash_test.cc
index 30a145a..655c222 100644
--- a/centipede/minimize_crash_test.cc
+++ b/centipede/minimize_crash_test.cc
@@ -23,6 +23,7 @@
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/base/nullability.h"
+#include "absl/types/span.h"
 #include "./centipede/centipede_callbacks.h"
 #include "./centipede/environment.h"
 #include "./centipede/runner_result.h"
@@ -40,10 +41,10 @@
   MinimizerMock(const Environment &env) : CentipedeCallbacks(env) {}
 
   // Runs FuzzMe() on every input, imitates failure if FuzzMe() returns true.
-  bool Execute(std::string_view binary, const std::vector<ByteArray> &inputs,
-               BatchResult &batch_result) override {
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
+               BatchResult& batch_result) override {
     batch_result.ClearAndResize(inputs.size());
-    for (auto &input : inputs) {
+    for (auto input : inputs) {
       if (FuzzMe(input)) {
         batch_result.exit_code() = EXIT_FAILURE;
         return false;
diff --git a/centipede/runner.cc b/centipede/runner.cc
index 4324529..1ccc2f8 100644
--- a/centipede/runner.cc
+++ b/centipede/runner.cc
@@ -49,6 +49,7 @@
 
 #include "absl/base/nullability.h"
 #include "absl/base/optimization.h"
+#include "absl/types/span.h"
 #include "./centipede/byte_array_mutator.h"
 #include "./centipede/dispatcher_flag_helper.h"
 #include "./centipede/execution_metadata.h"
@@ -327,7 +328,7 @@
 std::string RunnerCallbacks::GetSerializedTargetConfig() { return ""; }
 
 bool RunnerCallbacks::Mutate(
-    const std::vector<MutationInputRef>& /*inputs*/, size_t /*num_mutants*/,
+    absl::Span<const MutationInputRef> /*inputs*/, size_t /*num_mutants*/,
     std::function<void(MutantRef)> /*new_mutant_callback*/) {
   RunnerCheck(!HasCustomMutator(),
               "Class deriving from RunnerCallbacks must implement Mutate() if "
@@ -358,7 +359,7 @@
     return custom_mutator_cb_ != nullptr;
   }
 
-  bool Mutate(const std::vector<MutationInputRef>& inputs, size_t num_mutants,
+  bool Mutate(absl::Span<const MutationInputRef> inputs, size_t num_mutants,
               std::function<void(MutantRef)> new_mutant_callback) override;
 
  private:
@@ -632,7 +633,7 @@
 }
 
 bool LegacyRunnerCallbacks::Mutate(
-    const std::vector<MutationInputRef>& inputs, size_t num_mutants,
+    absl::Span<const MutationInputRef> inputs, size_t num_mutants,
     std::function<void(MutantRef)> new_mutant_callback) {
   if (custom_mutator_cb_ == nullptr) return false;
   unsigned int seed = GetRandomSeed();
diff --git a/centipede/runner_interface.h b/centipede/runner_interface.h
index cfbf849..c622daf 100644
--- a/centipede/runner_interface.h
+++ b/centipede/runner_interface.h
@@ -25,6 +25,7 @@
 #include <vector>
 
 #include "absl/base/nullability.h"
+#include "absl/types/span.h"
 #include "./centipede/mutation_data.h"
 #include "./common/defs.h"
 
@@ -153,7 +154,7 @@
   //
   // TODO(xinhaoyuan): Consider supporting only_shrink to speed up
   // input shrinking.
-  virtual bool Mutate(const std::vector<MutationInputRef>& inputs,
+  virtual bool Mutate(absl::Span<const MutationInputRef> inputs,
                       size_t num_mutants,
                       std::function<void(MutantRef)> new_mutant_callback);
   virtual ~RunnerCallbacks() = default;
diff --git a/centipede/runner_request.cc b/centipede/runner_request.cc
index 6a33cb7..3cdfbad 100644
--- a/centipede/runner_request.cc
+++ b/centipede/runner_request.cc
@@ -17,6 +17,7 @@
 #include <cstring>
 #include <vector>
 
+#include "absl/types/span.h"
 #include "./centipede/execution_metadata.h"
 #include "./centipede/mutation_data.h"
 #include "./centipede/shared_memory_blob_sequence.h"
@@ -37,8 +38,8 @@
 };
 
 // Writes `inputs` to `blobseq`, returns the number of inputs written.
-static size_t WriteInputs(const std::vector<ByteArray> &inputs,
-                          BlobSequence &blobseq) {
+static size_t WriteInputs(absl::Span<const ByteSpan> inputs,
+                          BlobSequence& blobseq) {
   size_t num_inputs = inputs.size();
   if (!blobseq.Write(kTagNumInputs, num_inputs)) return 0;
   size_t result = 0;
@@ -59,8 +60,8 @@
 }
 
 // Similar to above, but for mutation inputs.
-static size_t WriteInputs(const std::vector<MutationInputRef> &inputs,
-                          BlobSequence &blobseq) {
+static size_t WriteInputs(absl::Span<const MutationInputRef> inputs,
+                          BlobSequence& blobseq) {
   size_t num_inputs = inputs.size();
   if (!blobseq.Write(kTagNumInputs, num_inputs)) return 0;
   size_t result = 0;
@@ -75,15 +76,15 @@
 
 }  // namespace
 
-size_t RequestExecution(const std::vector<ByteArray> &inputs,
-                        BlobSequence &blobseq) {
+size_t RequestExecution(absl::Span<const ByteSpan> inputs,
+                        BlobSequence& blobseq) {
   if (!blobseq.Write({kTagExecution, 0, nullptr})) return 0;
   return WriteInputs(inputs, blobseq);
 }
 
 size_t RequestMutation(size_t num_mutants,
-                       const std::vector<MutationInputRef> &inputs,
-                       BlobSequence &blobseq) {
+                       absl::Span<const MutationInputRef> inputs,
+                       BlobSequence& blobseq) {
   if (!blobseq.Write({kTagMutation, 0, nullptr})) return 0;
   if (!blobseq.Write(kTagNumMutants, num_mutants)) return 0;
   return WriteInputs(inputs, blobseq);
diff --git a/centipede/runner_request.h b/centipede/runner_request.h
index f95189f..f1d7a5b 100644
--- a/centipede/runner_request.h
+++ b/centipede/runner_request.h
@@ -20,6 +20,7 @@
 #include <cstddef>
 #include <vector>
 
+#include "absl/types/span.h"
 #include "./centipede/execution_metadata.h"
 #include "./centipede/mutation_data.h"
 #include "./centipede/shared_memory_blob_sequence.h"
@@ -35,14 +36,14 @@
 
 // Sends a request (via `blobseq`) to execute `inputs`.
 // Returns the number of sent inputs, which would normally be inputs.size().
-size_t RequestExecution(const std::vector<ByteArray> &inputs,
-                        BlobSequence &blobseq);
+size_t RequestExecution(absl::Span<const ByteSpan> inputs,
+                        BlobSequence& blobseq);
 
 // Sends a request (via `blobseq`) to compute `num_mutants` mutants of `inputs`.
 // Returns the number of sent inputs, which would normally be inputs.size().
 size_t RequestMutation(size_t num_mutants,
-                       const std::vector<MutationInputRef> &inputs,
-                       BlobSequence &blobseq);
+                       absl::Span<const MutationInputRef> inputs,
+                       BlobSequence& blobseq);
 
 // Returns whether `blob` indicates an execution request.
 bool IsExecutionRequest(Blob blob);
diff --git a/centipede/test_coverage_util.cc b/centipede/test_coverage_util.cc
index 2602a58..7a995a0 100644
--- a/centipede/test_coverage_util.cc
+++ b/centipede/test_coverage_util.cc
@@ -18,6 +18,7 @@
 #include <string>
 #include <vector>
 
+#include "absl/types/span.h"
 #include "./centipede/corpus.h"
 #include "./centipede/environment.h"
 #include "./centipede/feature.h"
@@ -41,7 +42,9 @@
   }
   BatchResult batch_result;
   // Run.
-  CBs.Execute(env.binary, byte_array_inputs, batch_result);
+  std::vector<ByteSpan> byte_span_inputs(byte_array_inputs.begin(),
+                                         byte_array_inputs.end());
+  CBs.Execute(env.binary, byte_span_inputs, batch_result);
 
   // Repackage execution results into a vector of CorpusRecords.
   std::vector<CorpusRecord> corpus_records;
diff --git a/centipede/test_coverage_util.h b/centipede/test_coverage_util.h
index 4a61ab3..a338524 100644
--- a/centipede/test_coverage_util.h
+++ b/centipede/test_coverage_util.h
@@ -21,6 +21,7 @@
 #include <string_view>
 #include <vector>
 
+#include "absl/types/span.h"
 #include "./centipede/centipede_callbacks.h"
 #include "./centipede/corpus.h"
 #include "./centipede/environment.h"
@@ -43,14 +44,14 @@
 class TestCallbacks : public CentipedeCallbacks {
  public:
   explicit TestCallbacks(const Environment &env) : CentipedeCallbacks(env) {}
-  bool Execute(std::string_view binary, const std::vector<ByteArray> &inputs,
-               BatchResult &batch_result) override {
+  bool Execute(std::string_view binary, absl::Span<const ByteSpan> inputs,
+               BatchResult& batch_result) override {
     int result =
         ExecuteCentipedeSancovBinaryWithShmem(binary, inputs, batch_result);
     FUZZTEST_CHECK_EQ(EXIT_SUCCESS, result);
     return true;
   }
-  std::vector<Mutant> Mutate(const std::vector<MutationInputRef>& inputs,
+  std::vector<Mutant> Mutate(absl::Span<const MutationInputRef> inputs,
                              size_t num_mutants) override {
     return {};
   }
diff --git a/centipede/testing/BUILD b/centipede/testing/BUILD
index 216d83e..9a61fcd 100644
--- a/centipede/testing/BUILD
+++ b/centipede/testing/BUILD
@@ -84,6 +84,7 @@
         "@abseil-cpp//absl/base:nullability",
         "@abseil-cpp//absl/flags:flag",
         "@abseil-cpp//absl/flags:parse",
+        "@abseil-cpp//absl/types:span",
         "@com_google_fuzztest//centipede:centipede_runner_no_main",
         "@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 3d81ebc..b1b8f89 100644
--- a/centipede/testing/async_failing_target.cc
+++ b/centipede/testing/async_failing_target.cc
@@ -31,7 +31,7 @@
     return true;
   }
 
-  bool Mutate(const std::vector<fuzztest::internal::MutationInputRef>& inputs,
+  bool Mutate(absl::Span<const fuzztest::internal::MutationInputRef> inputs,
               size_t num_mutants,
               std::function<void(fuzztest::internal::MutantRef)>
                   new_mutant_callback) override {
diff --git a/centipede/testing/fuzz_target_with_custom_mutator.cc b/centipede/testing/fuzz_target_with_custom_mutator.cc
index 980ab99..ec686ed 100644
--- a/centipede/testing/fuzz_target_with_custom_mutator.cc
+++ b/centipede/testing/fuzz_target_with_custom_mutator.cc
@@ -19,6 +19,7 @@
 #include "absl/base/nullability.h"
 #include "absl/flags/flag.h"
 #include "absl/flags/parse.h"
+#include "absl/types/span.h"
 #include "./centipede/mutation_data.h"
 #include "./centipede/runner_interface.h"
 #include "./common/defs.h"
@@ -37,7 +38,7 @@
 
   bool HasCustomMutator() const override { return true; }
 
-  bool Mutate(const std::vector<fuzztest::internal::MutationInputRef>& inputs,
+  bool Mutate(absl::Span<const fuzztest::internal::MutationInputRef> inputs,
               size_t num_mutants,
               std::function<void(MutantRef)> new_mutant_callback) override {
     for (size_t i = 0; i < inputs.size() && i < num_mutants; ++i) {
diff --git a/centipede/util.cc b/centipede/util.cc
index 376d7b4..83da9e7 100644
--- a/centipede/util.cc
+++ b/centipede/util.cc
@@ -226,13 +226,11 @@
   return res;
 }
 
-ByteArray PackFeaturesAndHash(const ByteArray &data,
-                              const FeatureVec &features) {
+ByteArray PackFeaturesAndHash(ByteSpan data, const FeatureVec& features) {
   return PackFeaturesAndHashAsRawBytes(data, AsByteSpan(features));
 }
 
-ByteArray PackFeaturesAndHashAsRawBytes(const ByteArray &data,
-                                        ByteSpan features) {
+ByteArray PackFeaturesAndHashAsRawBytes(ByteSpan data, ByteSpan features) {
   ByteArray feature_bytes_with_hash(features.size() + kHashLen);
   auto hash = Hash(data);
   FUZZTEST_CHECK_EQ(hash.size(), kHashLen);
diff --git a/centipede/util.h b/centipede/util.h
index a34feda..ac5e963 100644
--- a/centipede/util.h
+++ b/centipede/util.h
@@ -140,12 +140,10 @@
 std::string ExtractHashFromArray(ByteArray &ba);
 
 // Pack {features, Hash(data)} into a byte array.
-ByteArray PackFeaturesAndHash(const ByteArray &data,
-                              const FeatureVec &features);
+ByteArray PackFeaturesAndHash(ByteSpan data, const FeatureVec& features);
 
 // Pack `features` and the hash of `data` directly from their raw data format.
-ByteArray PackFeaturesAndHashAsRawBytes(const ByteArray &data,
-                                        ByteSpan features);
+ByteArray PackFeaturesAndHashAsRawBytes(ByteSpan data, ByteSpan features);
 
 // Given a `blob` created by `PackFeaturesAndHash`, unpack the features into
 // `features` and return the hash.
diff --git a/common/remote_file.cc b/common/remote_file.cc
index b628ad6..a729e0c 100644
--- a/common/remote_file.cc
+++ b/common/remote_file.cc
@@ -30,8 +30,7 @@
 
 absl::Status RemoteFileAppend(RemoteFile *absl_nonnull f,
                               const std::string &contents) {
-  ByteArray contents_ba{contents.cbegin(), contents.cend()};
-  return RemoteFileAppend(f, contents_ba);
+  return RemoteFileAppend(f, AsByteSpan(contents));
 }
 
 absl::Status RemoteFileRead(RemoteFile *absl_nonnull f, std::string &contents) {
@@ -42,18 +41,11 @@
 }
 
 absl::Status RemoteFileSetContents(std::string_view path,
-                                   const ByteArray &contents) {
-  ASSIGN_OR_RETURN_IF_NOT_OK(RemoteFile * file, RemoteFileOpen(path, "w"));
-  if (file == nullptr) {
-    return absl::UnknownError(
-        "RemoteFileOpen returned an OK status but a nullptr RemoteFile*");
-  }
-  RETURN_IF_NOT_OK(RemoteFileAppend(file, contents));
-  return RemoteFileClose(file);
+                                   const std::string& contents) {
+  return RemoteFileSetContents(path, AsByteSpan(contents));
 }
 
-absl::Status RemoteFileSetContents(std::string_view path,
-                                   const std::string &contents) {
+absl::Status RemoteFileSetContents(std::string_view path, ByteSpan contents) {
   ASSIGN_OR_RETURN_IF_NOT_OK(RemoteFile * file, RemoteFileOpen(path, "w"));
   if (file == nullptr) {
     return absl::UnknownError(
diff --git a/common/remote_file.h b/common/remote_file.h
index e59b484..0b70444 100644
--- a/common/remote_file.h
+++ b/common/remote_file.h
@@ -74,13 +74,13 @@
 absl::Status RemoteFileSetWriteBufferSize(RemoteFile *absl_nonnull f,
                                           size_t size);
 
-// Appends bytes from 'ba' to 'f'.
-absl::Status RemoteFileAppend(RemoteFile *absl_nonnull f, const ByteArray &ba);
-
 // Appends characters from 'contents' to 'f'.
 absl::Status RemoteFileAppend(RemoteFile *absl_nonnull f,
                               const std::string &contents);
 
+// Appends bytes from 'contents' to 'f'.
+absl::Status RemoteFileAppend(RemoteFile* absl_nonnull f, ByteSpan contents);
+
 // Flushes the file's internal buffer. Some dynamic results of a running
 // pipeline are consumed by itself (e.g. shard cross-pollination) and can be
 // consumed by external processes (e.g. monitoring): for such files, call this
@@ -99,11 +99,10 @@
 
 // Sets the contents of the file at 'path' to 'contents'.
 absl::Status RemoteFileSetContents(std::string_view path,
-                                   const ByteArray &contents);
+                                   const std::string& contents);
 
 // Sets the contents of the file at 'path' to 'contents'.
-absl::Status RemoteFileSetContents(std::string_view path,
-                                   const std::string &contents);
+absl::Status RemoteFileSetContents(std::string_view path, ByteSpan contents);
 
 // Reads the contents of the file at 'path' into 'contents'.
 absl::Status RemoteFileGetContents(std::string_view path, ByteArray &contents);
diff --git a/common/remote_file_oss.cc b/common/remote_file_oss.cc
index fb6f23a..10299f6 100644
--- a/common/remote_file_oss.cc
+++ b/common/remote_file_oss.cc
@@ -90,11 +90,11 @@
     return absl::OkStatus();
   }
 
-  absl::Status Write(const ByteArray &ba) {
-    static constexpr auto elt_size = sizeof(ba[0]);
-    const auto elts_to_write = ba.size();
+  absl::Status Write(ByteSpan contents) {
+    static constexpr auto elt_size = sizeof(contents[0]);
+    const auto elts_to_write = contents.size();
     const auto elts_written =
-        std::fwrite(ba.data(), elt_size, elts_to_write, file_);
+        std::fwrite(contents.data(), elt_size, elts_to_write, file_);
     if (elts_written != elts_to_write) {
       return absl::UnknownError(absl::StrCat(
           "fwrite() wrote less elements that expected, wrote: ", elts_written,
@@ -327,8 +327,8 @@
   return static_cast<LocalRemoteFile *>(f)->SetWriteBufSize(size);
 }
 
-absl::Status RemoteFileAppend(RemoteFile *absl_nonnull f, const ByteArray &ba) {
-  return static_cast<LocalRemoteFile *>(f)->Write(ba);
+absl::Status RemoteFileAppend(RemoteFile* absl_nonnull f, ByteSpan contents) {
+  return static_cast<LocalRemoteFile*>(f)->Write(contents);
 }
 
 absl::Status RemoteFileFlush(RemoteFile *absl_nonnull f) {
diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc
index b323e2f..33b2628 100644
--- a/fuzztest/internal/centipede_adaptor.cc
+++ b/fuzztest/internal/centipede_adaptor.cc
@@ -516,7 +516,7 @@
 
   bool HasCustomMutator() const override { return true; }
 
-  bool Mutate(const std::vector<MutationInputRef>& inputs, size_t num_mutants,
+  bool Mutate(absl::Span<const MutationInputRef> inputs, size_t num_mutants,
               std::function<void(MutantRef)> new_mutant_callback) override {
     if (inputs.empty()) return false;
     cmp_tables.resize(inputs.size());
@@ -1016,7 +1016,7 @@
   using fuzztest::internal::CentipedeCallbacks::CentipedeCallbacks;
 
   bool Execute(std::string_view binary,
-               const std::vector<fuzztest::internal::ByteArray>& inputs,
+               absl::Span<const fuzztest::internal::ByteSpan> inputs,
                fuzztest::internal::BatchResult& batch_result) override {
     return false;
   }