Change GetFileContents() test utility to return a vector

Instead of returning a std::unique_ptr, with a separate out-parameter
for the size, change GetFileContents() to just return a std::vector.
Because it is only used for reading data files in tests,
GetFileContents() should never have to read an empty file. Therefore, it
can use an empty vector to indicate failure, and not have to wrap the
vector in an optional.

Change-Id: I485551e2ed4c47e98f4535cf91bbf6bff1ca99bb
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/113452
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/fpdfsdk/fpdf_dataavail_embeddertest.cpp b/fpdfsdk/fpdf_dataavail_embeddertest.cpp
index 05867de..9718bb5 100644
--- a/fpdfsdk/fpdf_dataavail_embeddertest.cpp
+++ b/fpdfsdk/fpdf_dataavail_embeddertest.cpp
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "core/fxcrt/bytestring.h"
+#include "core/fxcrt/span_util.h"
 #include "public/fpdf_doc.h"
 #include "public/fpdfview.h"
 #include "testing/embedder_test.h"
@@ -41,12 +42,13 @@
     if (file_path.empty()) {
       return;
     }
-    file_contents_ = GetFileContents(file_path.c_str(), &file_length_);
-    if (!file_contents_)
+    file_contents_ = GetFileContents(file_path.c_str());
+    if (file_contents_.empty()) {
       return;
+    }
 
     file_access_.m_FileLen =
-        pdfium::base::checked_cast<unsigned long>(file_length_);
+        pdfium::base::checked_cast<unsigned long>(file_contents_.size());
     file_access_.m_GetBlock = SGetBlock;
     file_access_.m_Param = this;
 
@@ -57,7 +59,7 @@
     FX_FILEAVAIL::IsDataAvail = SIsDataAvail;
   }
 
-  bool IsOpened() const { return !!file_contents_; }
+  bool IsOpened() const { return !file_contents_.empty(); }
 
   FPDF_FILEACCESS* file_access() { return &file_access_; }
   FX_DOWNLOADHINTS* hints() { return this; }
@@ -92,8 +94,8 @@
     ClearRequestedSegments();
   }
 
-  char* file_contents() { return file_contents_.get(); }
-  size_t file_length() const { return file_length_; }
+  pdfium::span<const uint8_t> file_contents() const { return file_contents_; }
+  pdfium::span<uint8_t> mutable_file_contents() { return file_contents_; }
 
  private:
   void SetDataAvailable(size_t start, size_t size) {
@@ -108,12 +110,15 @@
     if (!IsDataAvailImpl(pos, size))
       return 0;
     const unsigned long end = std::min(
-        pdfium::base::checked_cast<unsigned long>(file_length_), pos + size);
+        pdfium::base::checked_cast<unsigned long>(file_contents_.size()),
+        pos + size);
     if (end <= pos)
       return 0;
-    memcpy(pBuf, file_contents_.get() + pos, end - pos);
-    SetDataAvailable(pos, end - pos);
-    return static_cast<int>(end - pos);
+    const unsigned long bytes_to_copy = end - pos;
+    fxcrt::spancpy(pdfium::make_span(pBuf, size),
+                   file_contents().subspan(pos, bytes_to_copy));
+    SetDataAvailable(pos, bytes_to_copy);
+    return static_cast<int>(bytes_to_copy);
   }
 
   void AddSegmentImpl(size_t offset, size_t size) {
@@ -122,8 +127,9 @@
   }
 
   bool IsDataAvailImpl(size_t offset, size_t size) {
-    if (offset + size > file_length_)
+    if (offset + size > file_contents_.size()) {
       return false;
+    }
     if (is_new_data_available_) {
       SetDataAvailable(offset, size);
       return true;
@@ -150,8 +156,7 @@
 
   FPDF_FILEACCESS file_access_;
 
-  std::unique_ptr<char, pdfium::FreeDeleter> file_contents_;
-  size_t file_length_ = 0;
+  std::vector<uint8_t> file_contents_;
   std::vector<std::pair<size_t, size_t>> requested_segments_;
   size_t max_requested_bound_ = 0;
   bool is_new_data_available_ = true;
@@ -303,10 +308,13 @@
   TestAsyncLoader loader("linearized.pdf");
   // Map "Info" to an object within the first section without breaking
   // linearization.
-  ByteString data(loader.file_contents(), loader.file_length());
+  ByteString data(ByteStringView(loader.file_contents()));
   absl::optional<size_t> index = data.Find("/Info 27 0 R");
-  ASSERT_TRUE(index);
-  memcpy(loader.file_contents() + *index, "/Info 29 0 R", 12);
+  ASSERT_TRUE(index.has_value());
+  auto span = loader.mutable_file_contents().subspan(index.value()).subspan(7);
+  ASSERT_FALSE(span.empty());
+  EXPECT_EQ('7', span[0]);
+  span[0] = '9';
 
   loader.set_is_new_data_available(false);
   CreateAvail(loader.file_avail(), loader.file_access());
@@ -328,10 +336,15 @@
 TEST_F(FPDFDataAvailEmbedderTest, TryLoadInvalidInfo) {
   TestAsyncLoader loader("linearized.pdf");
   // Map "Info" to an invalid object without breaking linearization.
-  ByteString data(loader.file_contents(), loader.file_length());
+  ByteString data(ByteStringView(loader.file_contents()));
   absl::optional<size_t> index = data.Find("/Info 27 0 R");
-  ASSERT_TRUE(index);
-  memcpy(loader.file_contents() + *index, "/Info 99 0 R", 12);
+  ASSERT_TRUE(index.has_value());
+  auto span = loader.mutable_file_contents().subspan(index.value()).subspan(6);
+  ASSERT_GE(span.size(), 2u);
+  EXPECT_EQ('2', span[0]);
+  EXPECT_EQ('7', span[1]);
+  span[0] = '9';
+  span[1] = '9';
 
   loader.set_is_new_data_available(false);
   CreateAvail(loader.file_avail(), loader.file_access());
@@ -354,10 +367,13 @@
 TEST_F(FPDFDataAvailEmbedderTest, TryLoadNonExistsInfo) {
   TestAsyncLoader loader("linearized.pdf");
   // Break the "Info" parameter without breaking linearization.
-  ByteString data(loader.file_contents(), loader.file_length());
+  ByteString data(ByteStringView(loader.file_contents()));
   absl::optional<size_t> index = data.Find("/Info 27 0 R");
-  ASSERT_TRUE(index);
-  memcpy(loader.file_contents() + *index, "/I_fo 27 0 R", 12);
+  ASSERT_TRUE(index.has_value());
+  auto span = loader.mutable_file_contents().subspan(index.value()).subspan(2);
+  ASSERT_FALSE(span.empty());
+  EXPECT_EQ('n', span[0]);
+  span[0] = '_';
 
   loader.set_is_new_data_available(false);
   CreateAvail(loader.file_avail(), loader.file_access());
diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp
index 2f29e3d..00cc7b2 100644
--- a/fpdfsdk/fpdf_edit_embeddertest.cpp
+++ b/fpdfsdk/fpdf_edit_embeddertest.cpp
@@ -290,14 +290,12 @@
   ASSERT_TRUE(PathService::GetThirdPartyFilePath(
       "NotoSansCJK/NotoSansSC-Regular.subset.otf", &font_path));
 
-  size_t file_length = 0;
-  std::unique_ptr<char, pdfium::FreeDeleter> font_data =
-      GetFileContents(font_path.c_str(), &file_length);
-  ASSERT_TRUE(font_data);
+  std::vector<uint8_t> font_data = GetFileContents(font_path.c_str());
+  ASSERT_FALSE(font_data.empty());
 
-  ScopedFPDFFont font(FPDFText_LoadFont(
-      document(), reinterpret_cast<const uint8_t*>(font_data.get()),
-      file_length, FPDF_FONT_TRUETYPE, /*cid=*/true));
+  ScopedFPDFFont font(FPDFText_LoadFont(document(), font_data.data(),
+                                        font_data.size(), FPDF_FONT_TRUETYPE,
+                                        /*cid=*/true));
   FPDF_PAGEOBJECT text_object =
       FPDFPageObj_CreateTextObj(document(), font.get(), 20.0f);
   EXPECT_TRUE(text_object);
@@ -337,14 +335,12 @@
   ASSERT_TRUE(PathService::GetThirdPartyFilePath(
       "NotoSansCJK/NotoSansSC-Regular.subset.otf", &font_path));
 
-  size_t file_length = 0;
-  std::unique_ptr<char, pdfium::FreeDeleter> font_data =
-      GetFileContents(font_path.c_str(), &file_length);
-  ASSERT_TRUE(font_data);
+  std::vector<uint8_t> font_data = GetFileContents(font_path.c_str());
+  ASSERT_FALSE(font_data.empty());
 
-  ScopedFPDFFont font(FPDFText_LoadFont(
-      document(), reinterpret_cast<const uint8_t*>(font_data.get()),
-      file_length, FPDF_FONT_TRUETYPE, /*cid=*/true));
+  ScopedFPDFFont font(FPDFText_LoadFont(document(), font_data.data(),
+                                        font_data.size(), FPDF_FONT_TRUETYPE,
+                                        /*cid=*/true));
   FPDF_PAGEOBJECT text_object =
       FPDFPageObj_CreateTextObj(document(), font.get(), 20.0f);
   EXPECT_TRUE(text_object);
diff --git a/fpdfsdk/fpdf_ppo_embeddertest.cpp b/fpdfsdk/fpdf_ppo_embeddertest.cpp
index bc3d442..67e0cc2 100644
--- a/fpdfsdk/fpdf_ppo_embeddertest.cpp
+++ b/fpdfsdk/fpdf_ppo_embeddertest.cpp
@@ -5,6 +5,7 @@
 #include <iterator>
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "core/fpdfapi/page/cpdf_form.h"
 #include "core/fpdfapi/page/cpdf_formobject.h"
@@ -635,12 +636,11 @@
 
   std::string file_path = PathService::GetTestFilePath("rectangles.pdf");
   ASSERT_FALSE(file_path.empty());
-  size_t file_length = 0;
-  std::unique_ptr<char, pdfium::FreeDeleter> file_contents =
-      GetFileContents(file_path.c_str(), &file_length);
-  DCHECK(file_contents);
-  ScopedFPDFDocument src_doc(
-      FPDF_LoadMemDocument(file_contents.get(), file_length, nullptr));
+  std::vector<uint8_t> file_contents = GetFileContents(file_path.c_str());
+  ASSERT_FALSE(file_contents.empty());
+
+  ScopedFPDFDocument src_doc(FPDF_LoadMemDocument(
+      file_contents.data(), file_contents.size(), nullptr));
   ASSERT_TRUE(src_doc);
 
   static constexpr int kIndices[] = {0};
diff --git a/fpdfsdk/fpdf_view_embeddertest.cpp b/fpdfsdk/fpdf_view_embeddertest.cpp
index 4f1c9e3..0e9fb33 100644
--- a/fpdfsdk/fpdf_view_embeddertest.cpp
+++ b/fpdfsdk/fpdf_view_embeddertest.cpp
@@ -4,6 +4,7 @@
 
 #include <math.h>
 
+#include <algorithm>
 #include <limits>
 #include <memory>
 #include <string>
@@ -25,7 +26,6 @@
 #include "testing/utils/file_util.h"
 #include "testing/utils/hash.h"
 #include "testing/utils/path_service.h"
-#include "third_party/base/check.h"
 
 #if defined(_SKIA_SUPPORT_)
 #include "third_party/skia/include/core/SkCanvas.h"           // nogncheck
@@ -510,12 +510,10 @@
   std::string file_path = PathService::GetTestFilePath("about_blank.pdf");
   ASSERT_FALSE(file_path.empty());
 
-  size_t file_length = 0;
-  std::unique_ptr<char, pdfium::FreeDeleter> file_contents =
-      GetFileContents(file_path.c_str(), &file_length);
-  DCHECK(file_contents);
-  ScopedFPDFDocument doc(
-      FPDF_LoadMemDocument64(file_contents.get(), file_length, nullptr));
+  std::vector<uint8_t> file_contents = GetFileContents(file_path.c_str());
+  ASSERT_FALSE(file_contents.empty());
+  ScopedFPDFDocument doc(FPDF_LoadMemDocument64(file_contents.data(),
+                                                file_contents.size(), nullptr));
   ASSERT_TRUE(doc);
 
   int version;
@@ -610,18 +608,17 @@
   ScopedFPDFDocument doc;
   {
     // Read a PDF, and copy it into |file_contents_string|.
-    size_t pdf_length;
     std::string pdf_path = PathService::GetTestFilePath("rectangles.pdf");
     ASSERT_FALSE(pdf_path.empty());
-    auto file_contents = GetFileContents(pdf_path.c_str(), &pdf_length);
-    ASSERT_TRUE(file_contents);
-    for (size_t i = 0; i < pdf_length; ++i)
-      file_contents_string.push_back(file_contents.get()[i]);
+    std::vector<uint8_t> file_contents = GetFileContents(pdf_path.c_str());
+    ASSERT_FALSE(file_contents.empty());
+    std::copy(file_contents.begin(), file_contents.end(),
+              std::back_inserter(file_contents_string));
 
     // Define a FPDF_FILEACCESS object that will go out of scope, while the
     // loaded document in |doc| remains valid.
     FPDF_FILEACCESS file_access = {};
-    file_access.m_FileLen = pdf_length;
+    file_access.m_FileLen = file_contents_string.size();
     file_access.m_GetBlock = GetBlockFromString;
     file_access.m_Param = &file_contents_string;
     doc.reset(FPDF_LoadCustomDocument(&file_access, nullptr));
@@ -1440,12 +1437,10 @@
     EXPECT_TRUE(FPDF_DocumentHasValidCrossReferenceTable(doc.get()));
   }
   {
-    size_t file_length = 0;
-    std::unique_ptr<char, pdfium::FreeDeleter> file_contents =
-        GetFileContents(file_path.c_str(), &file_length);
-    DCHECK(file_contents);
+    std::vector<uint8_t> file_contents = GetFileContents(file_path.c_str());
+    ASSERT_FALSE(file_contents.empty());
     ScopedFPDFDocument doc(
-        FPDF_LoadMemDocument(file_contents.get(), file_length, ""));
+        FPDF_LoadMemDocument(file_contents.data(), file_contents.size(), ""));
     ASSERT_TRUE(doc);
     EXPECT_TRUE(FPDF_DocumentHasValidCrossReferenceTable(doc.get()));
   }
diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc
index cb45a49..a59b8ea 100644
--- a/samples/pdfium_test.cc
+++ b/samples/pdfium_test.cc
@@ -9,6 +9,7 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <algorithm>
 #include <functional>
 #include <iterator>
 #include <map>
@@ -825,7 +826,7 @@
   void Idle() const { idler()(); }
 
   void ProcessPdf(const std::string& name,
-                  pdfium::span<const char> data,
+                  pdfium::span<const uint8_t> data,
                   const std::string& events);
 
  private:
@@ -1525,7 +1526,7 @@
 }
 
 void Processor::ProcessPdf(const std::string& name,
-                           pdfium::span<const char> data,
+                           pdfium::span<const uint8_t> data,
                            const std::string& events) {
   TestLoader loader(data);
 
@@ -1957,11 +1958,10 @@
 
   Processor processor(&options, &idler);
   for (const std::string& filename : files) {
-    size_t file_length = 0;
-    std::unique_ptr<char, pdfium::FreeDeleter> file_contents =
-        GetFileContents(filename.c_str(), &file_length);
-    if (!file_contents)
+    std::vector<uint8_t> file_contents = GetFileContents(filename.c_str());
+    if (file_contents.empty()) {
       continue;
+    }
     fprintf(stderr, "Processing PDF file %s.\n", filename.c_str());
 
 #ifdef ENABLE_CALLGRIND
@@ -1972,24 +1972,24 @@
     std::string events;
     if (options.send_events) {
       std::string event_filename = filename;
-      size_t event_length = 0;
       size_t extension_pos = event_filename.find(".pdf");
       if (extension_pos != std::string::npos) {
         event_filename.replace(extension_pos, 4, ".evt");
         if (access(event_filename.c_str(), R_OK) == 0) {
           fprintf(stderr, "Using event file %s.\n", event_filename.c_str());
-          std::unique_ptr<char, pdfium::FreeDeleter> event_contents =
-              GetFileContents(event_filename.c_str(), &event_length);
-          if (event_contents) {
+          std::vector<uint8_t> event_contents =
+              GetFileContents(event_filename.c_str());
+          if (!event_contents.empty()) {
             fprintf(stderr, "Sending events from: %s\n",
                     event_filename.c_str());
-            events = std::string(event_contents.get(), event_length);
+            std::copy(event_contents.begin(), event_contents.end(),
+                      std::back_inserter(events));
           }
         }
       }
     }
 
-    processor.ProcessPdf(filename, {file_contents.get(), file_length}, events);
+    processor.ProcessPdf(filename, file_contents, events);
 
 #ifdef ENABLE_CALLGRIND
     if (options.callgrind_delimiters)
diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp
index 5aa9fc7..cc9be36 100644
--- a/testing/embedder_test.cpp
+++ b/testing/embedder_test.cpp
@@ -333,17 +333,17 @@
     return false;
   }
 
-  file_contents_ = GetFileContents(file_path.c_str(), &file_length_);
-  if (!file_contents_)
+  file_contents_ = GetFileContents(file_path.c_str());
+  if (file_contents_.empty()) {
     return false;
+  }
 
   EXPECT_TRUE(!loader_);
-  loader_ = std::make_unique<TestLoader>(
-      pdfium::make_span(file_contents_.get(), file_length_));
+  loader_ = std::make_unique<TestLoader>(file_contents_);
 
   memset(&file_access_, 0, sizeof(file_access_));
   file_access_.m_FileLen =
-      pdfium::base::checked_cast<unsigned long>(file_length_);
+      pdfium::base::checked_cast<unsigned long>(file_contents_.size());
   file_access_.m_GetBlock = TestLoader::GetBlock;
   file_access_.m_Param = loader_.get();
 
@@ -428,7 +428,7 @@
   fake_file_access_.reset();
   memset(&file_access_, 0, sizeof(file_access_));
   loader_.reset();
-  file_contents_.reset();
+  file_contents_ = {};
 }
 
 FPDF_FORMHANDLE EmbedderTest::SetupFormFillEnvironment(
diff --git a/testing/embedder_test.h b/testing/embedder_test.h
index 829b1aa..ef0ac8e 100644
--- a/testing/embedder_test.h
+++ b/testing/embedder_test.h
@@ -5,6 +5,8 @@
 #ifndef TESTING_EMBEDDER_TEST_H_
 #define TESTING_EMBEDDER_TEST_H_
 
+#include <stdint.h>
+
 #include <fstream>
 #include <map>
 #include <memory>
@@ -19,7 +21,6 @@
 #include "public/fpdf_save.h"
 #include "public/fpdfview.h"
 #include "testing/fake_file_access.h"
-#include "testing/free_deleter.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/base/containers/span.h"
 
@@ -300,9 +301,8 @@
   int form_fill_info_version_ = 1;
 #endif  // PDF_ENABLE_XFA
 
-  size_t file_length_ = 0;
   // must outlive `loader_`.
-  std::unique_ptr<char, pdfium::FreeDeleter> file_contents_;
+  std::vector<uint8_t> file_contents_;
   std::unique_ptr<TestLoader> loader_;
   FPDF_FILEACCESS file_access_;                       // must outlive `avail_`.
   std::unique_ptr<FakeFileAccess> fake_file_access_;  // must outlive `avail_`.
diff --git a/testing/test_loader.cpp b/testing/test_loader.cpp
index a3fad8a..c39631d 100644
--- a/testing/test_loader.cpp
+++ b/testing/test_loader.cpp
@@ -9,7 +9,7 @@
 #include "third_party/base/check_op.h"
 #include "third_party/base/numerics/checked_math.h"
 
-TestLoader::TestLoader(pdfium::span<const char> span) : m_Span(span) {}
+TestLoader::TestLoader(pdfium::span<const uint8_t> span) : m_Span(span) {}
 
 // static
 int TestLoader::GetBlock(void* param,
diff --git a/testing/test_loader.h b/testing/test_loader.h
index d0e83c5..77f6be6 100644
--- a/testing/test_loader.h
+++ b/testing/test_loader.h
@@ -5,11 +5,13 @@
 #ifndef TESTING_TEST_LOADER_H_
 #define TESTING_TEST_LOADER_H_
 
+#include <stdint.h>
+
 #include "third_party/base/containers/span.h"
 
 class TestLoader {
  public:
-  explicit TestLoader(pdfium::span<const char> span);
+  explicit TestLoader(pdfium::span<const uint8_t> span);
 
   static int GetBlock(void* param,
                       unsigned long pos,
@@ -17,7 +19,7 @@
                       unsigned long size);
 
  private:
-  const pdfium::span<const char> m_Span;
+  const pdfium::span<const uint8_t> m_Span;
 };
 
 #endif  // TESTING_TEST_LOADER_H_
diff --git a/testing/utils/file_util.cpp b/testing/utils/file_util.cpp
index 439f849..9a4a34f 100644
--- a/testing/utils/file_util.cpp
+++ b/testing/utils/file_util.cpp
@@ -5,36 +5,33 @@
 #include "testing/utils/file_util.h"
 
 #include <stdio.h>
-#include <string.h>
 
+#include <utility>
+#include <vector>
+
+#include "core/fxcrt/span_util.h"
 #include "testing/utils/path_service.h"
 #include "third_party/base/numerics/safe_conversions.h"
 
-std::unique_ptr<char, pdfium::FreeDeleter> GetFileContents(const char* filename,
-                                                           size_t* retlen) {
+std::vector<uint8_t> GetFileContents(const char* filename) {
   FILE* file = fopen(filename, "rb");
   if (!file) {
     fprintf(stderr, "Failed to open: %s\n", filename);
-    return nullptr;
+    return {};
   }
   (void)fseek(file, 0, SEEK_END);
   size_t file_length = ftell(file);
   if (!file_length) {
-    return nullptr;
+    return {};
   }
   (void)fseek(file, 0, SEEK_SET);
-  std::unique_ptr<char, pdfium::FreeDeleter> buffer(
-      static_cast<char*>(malloc(file_length)));
-  if (!buffer) {
-    return nullptr;
-  }
-  size_t bytes_read = fread(buffer.get(), 1, file_length, file);
+  std::vector<uint8_t> buffer(file_length);
+  size_t bytes_read = fread(buffer.data(), 1, file_length, file);
   (void)fclose(file);
   if (bytes_read != file_length) {
     fprintf(stderr, "Failed to read: %s\n", filename);
-    return nullptr;
+    return {};
   }
-  *retlen = bytes_read;
   return buffer;
 }
 
@@ -44,11 +41,12 @@
     return;
   }
 
-  file_contents_ = GetFileContents(file_path.c_str(), &file_length_);
-  if (!file_contents_)
+  file_contents_ = GetFileContents(file_path.c_str());
+  if (file_contents_.empty()) {
     return;
+  }
 
-  m_FileLen = pdfium::base::checked_cast<unsigned long>(file_length_);
+  m_FileLen = pdfium::base::checked_cast<unsigned long>(file_contents_.size());
   m_GetBlock = SGetBlock;
   m_Param = this;
 }
@@ -56,8 +54,9 @@
 int FileAccessForTesting::GetBlockImpl(unsigned long pos,
                                        unsigned char* pBuf,
                                        unsigned long size) {
-  memcpy(pBuf, file_contents_.get() + pos, size);
-  return pdfium::base::checked_cast<int>(size);
+  fxcrt::spancpy(pdfium::make_span(pBuf, size),
+                 pdfium::make_span(file_contents_).subspan(pos, size));
+  return size ? 1 : 0;
 }
 
 // static
diff --git a/testing/utils/file_util.h b/testing/utils/file_util.h
index e91f3d4..ca8d1c3 100644
--- a/testing/utils/file_util.h
+++ b/testing/utils/file_util.h
@@ -5,17 +5,17 @@
 #ifndef TESTING_UTILS_FILE_UTIL_H_
 #define TESTING_UTILS_FILE_UTIL_H_
 
-#include <stdlib.h>
+#include <stdint.h>
 
-#include <memory>
 #include <string>
+#include <vector>
 
 #include "public/fpdfview.h"
-#include "testing/free_deleter.h"
 
-// Reads the entire contents of a file into a newly alloc'd buffer.
-std::unique_ptr<char, pdfium::FreeDeleter> GetFileContents(const char* filename,
-                                                           size_t* retlen);
+// Reads the entire contents of a file into a vector. Returns an empty vector on
+// failure. Note that this function assumes reading an empty file is not a valid
+// use case, and treats such an action as a failure.
+std::vector<uint8_t> GetFileContents(const char* filename);
 
 // Use an ordinary file anywhere a FPDF_FILEACCESS is required.
 class FileAccessForTesting final : public FPDF_FILEACCESS {
@@ -30,8 +30,7 @@
 
   int GetBlockImpl(unsigned long pos, unsigned char* pBuf, unsigned long size);
 
-  size_t file_length_;
-  std::unique_ptr<char, pdfium::FreeDeleter> file_contents_;
+  std::vector<uint8_t> file_contents_;
 };
 
 #endif  // TESTING_UTILS_FILE_UTIL_H_
diff --git a/testing/v8_initializer.cpp b/testing/v8_initializer.cpp
index f4ed611..9aa5670 100644
--- a/testing/v8_initializer.cpp
+++ b/testing/v8_initializer.cpp
@@ -4,7 +4,10 @@
 
 #include "testing/v8_initializer.h"
 
+#include <stdlib.h>
+
 #include <cstring>
+#include <vector>
 
 #include "public/fpdfview.h"
 #include "testing/utils/file_util.h"
@@ -48,14 +51,16 @@
                      v8::StartupData* result_data) {
   std::string full_path =
       GetFullPathForSnapshotFile(exe_path, bin_dir, filename);
-  size_t data_length = 0;
-  std::unique_ptr<char, pdfium::FreeDeleter> data_buffer =
-      GetFileContents(full_path.c_str(), &data_length);
-  if (!data_buffer)
+  std::vector<uint8_t> data_buffer = GetFileContents(full_path.c_str());
+  if (data_buffer.empty()) {
     return false;
+  }
 
-  result_data->data = data_buffer.release();
-  result_data->raw_size = pdfium::base::checked_cast<int>(data_length);
+  // `result_data` takes ownership.
+  void* copy = malloc(data_buffer.size());
+  memcpy(copy, data_buffer.data(), data_buffer.size());
+  result_data->data = static_cast<char*>(copy);
+  result_data->raw_size = pdfium::base::checked_cast<int>(data_buffer.size());
   return true;
 }
 #endif  // V8_USE_EXTERNAL_STARTUP_DATA