Implement FixedTryAllocZeroedDataVector.

This FixedSizeDataVector variant wraps FX_TryAlloc() instead of
FX_Alloc(), so it may not actually allocate the requested amount of
data. Add test cases for it similar to other FixedSizeDataVector
variants, with the addition of a deliberate memory allocation failure
case.

Bug: pdfium:1872
Change-Id: Id49ab84f5aa2a925b6b7c96e59e3fbda17a69110
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/100571
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/core/fxcrt/BUILD.gn b/core/fxcrt/BUILD.gn
index 28b0f34..00e46d2 100644
--- a/core/fxcrt/BUILD.gn
+++ b/core/fxcrt/BUILD.gn
@@ -41,6 +41,7 @@
     "data_vector.h",
     "fileaccess_iface.h",
     "fixed_size_data_vector.h",
+    "fixed_try_alloc_zeroed_data_vector.h",
     "fixed_uninit_data_vector.h",
     "fixed_zeroed_data_vector.h",
     "fx_bidi.cpp",
@@ -173,6 +174,7 @@
     "cfx_datetime_unittest.cpp",
     "cfx_seekablestreamproxy_unittest.cpp",
     "cfx_timer_unittest.cpp",
+    "fixed_try_alloc_zeroed_data_vector_unittest.cpp",
     "fixed_uninit_data_vector_unittest.cpp",
     "fixed_zeroed_data_vector_unittest.cpp",
     "fx_bidi_unittest.cpp",
diff --git a/core/fxcrt/fixed_size_data_vector.h b/core/fxcrt/fixed_size_data_vector.h
index 1f64926..c0adc8c 100644
--- a/core/fxcrt/fixed_size_data_vector.h
+++ b/core/fxcrt/fixed_size_data_vector.h
@@ -15,29 +15,34 @@
 
 namespace fxcrt {
 
+enum class DataVectorAllocOption {
+  kInitialized,
+  kUninitialized,
+  kTryInitialized,
+};
+
 // A simple data container that has a fixed size.
 // Unlike std::vector, it cannot be implicitly copied and its data is only
 // accessible using spans.
 // It can either initialize its data with zeros, or leave its data
 // uninitialized.
-template <typename T, bool INITIALIZE>
+template <typename T, DataVectorAllocOption OPTION>
 class FixedSizeDataVector {
  public:
   FixedSizeDataVector() : FixedSizeDataVector(0) {}
   explicit FixedSizeDataVector(size_t size)
-      : data_(MaybeInit(size, INITIALIZE)), size_(size) {}
+      : data_(MaybeInit(size, OPTION)), size_(CalculateSize(size, OPTION)) {}
   FixedSizeDataVector(const FixedSizeDataVector&) = delete;
   FixedSizeDataVector& operator=(const FixedSizeDataVector&) = delete;
-  template <bool OTHER_INITIALIZE>
-  FixedSizeDataVector(
-      FixedSizeDataVector<T, OTHER_INITIALIZE>&& that) noexcept {
+  template <DataVectorAllocOption OTHER_OPTION>
+  FixedSizeDataVector(FixedSizeDataVector<T, OTHER_OPTION>&& that) noexcept {
     data_ = std::move(that.data_);
     size_ = that.size_;
     that.size_ = 0;
   }
-  template <bool OTHER_INITIALIZE>
+  template <DataVectorAllocOption OTHER_OPTION>
   FixedSizeDataVector& operator=(
-      FixedSizeDataVector<T, OTHER_INITIALIZE>&& that) noexcept {
+      FixedSizeDataVector<T, OTHER_OPTION>&& that) noexcept {
     data_ = std::move(that.data_);
     size_ = that.size_;
     that.size_ = 0;
@@ -59,13 +64,31 @@
   bool empty() const { return size_ == 0; }
 
  private:
-  friend class FixedSizeDataVector<T, true>;
-  friend class FixedSizeDataVector<T, false>;
+  friend class FixedSizeDataVector<T, DataVectorAllocOption::kInitialized>;
+  friend class FixedSizeDataVector<T, DataVectorAllocOption::kUninitialized>;
+  friend class FixedSizeDataVector<T, DataVectorAllocOption::kTryInitialized>;
 
-  static T* MaybeInit(size_t size, bool initialize) {
+  static T* MaybeInit(size_t size, DataVectorAllocOption alloc_option) {
     if (size == 0)
       return nullptr;
-    return initialize ? FX_Alloc(T, size) : FX_AllocUninit(T, size);
+    switch (alloc_option) {
+      case DataVectorAllocOption::kInitialized:
+        return FX_Alloc(T, size);
+      case DataVectorAllocOption::kUninitialized:
+        return FX_AllocUninit(T, size);
+      case DataVectorAllocOption::kTryInitialized:
+        return FX_TryAlloc(T, size);
+    }
+  }
+
+  size_t CalculateSize(size_t size, DataVectorAllocOption alloc_option) const {
+    switch (alloc_option) {
+      case DataVectorAllocOption::kInitialized:
+      case DataVectorAllocOption::kUninitialized:
+        return size;
+      case DataVectorAllocOption::kTryInitialized:
+        return data_ ? size : 0;
+    }
   }
 
   std::unique_ptr<T, FxFreeDeleter> data_;
diff --git a/core/fxcrt/fixed_try_alloc_zeroed_data_vector.h b/core/fxcrt/fixed_try_alloc_zeroed_data_vector.h
new file mode 100644
index 0000000..e7f1bf8
--- /dev/null
+++ b/core/fxcrt/fixed_try_alloc_zeroed_data_vector.h
@@ -0,0 +1,17 @@
+// Copyright 2022 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CORE_FXCRT_FIXED_TRY_ALLOC_ZEROED_DATA_VECTOR_H_
+#define CORE_FXCRT_FIXED_TRY_ALLOC_ZEROED_DATA_VECTOR_H_
+
+#include "core/fxcrt/fixed_size_data_vector.h"
+
+// WARNING: Since FX_TryAlloc() can fail, one must always check if a
+// FixedTryAllocZeroedDataVector is empty after creating one.
+template <typename T>
+using FixedTryAllocZeroedDataVector =
+    fxcrt::FixedSizeDataVector<T,
+                               fxcrt::DataVectorAllocOption::kTryInitialized>;
+
+#endif  // CORE_FXCRT_FIXED_TRY_ALLOC_ZEROED_DATA_VECTOR_H_
diff --git a/core/fxcrt/fixed_try_alloc_zeroed_data_vector_unittest.cpp b/core/fxcrt/fixed_try_alloc_zeroed_data_vector_unittest.cpp
new file mode 100644
index 0000000..e80ba27
--- /dev/null
+++ b/core/fxcrt/fixed_try_alloc_zeroed_data_vector_unittest.cpp
@@ -0,0 +1,108 @@
+// Copyright 2022 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/fxcrt/fixed_try_alloc_zeroed_data_vector.h"
+
+#include <limits>
+#include <utility>
+
+#include "core/fxcrt/fixed_uninit_data_vector.h"
+#include "core/fxcrt/fixed_zeroed_data_vector.h"
+#include "core/fxcrt/span_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/base/span.h"
+
+TEST(FixedTryAllocZeroedDataVector, NoData) {
+  FixedTryAllocZeroedDataVector<int> vec;
+  EXPECT_EQ(0u, vec.size());
+  EXPECT_TRUE(vec.empty());
+  EXPECT_TRUE(vec.span().empty());
+  EXPECT_TRUE(vec.writable_span().empty());
+}
+
+TEST(FixedTryAllocZeroedDataVector, WithData) {
+  FixedTryAllocZeroedDataVector<int> vec(4);
+  EXPECT_FALSE(vec.empty());
+  EXPECT_EQ(4u, vec.size());
+  EXPECT_EQ(4u, vec.span().size());
+  EXPECT_EQ(4u, vec.writable_span().size());
+  EXPECT_THAT(vec.span(), testing::ElementsAre(0, 0, 0, 0));
+
+  constexpr int kData[] = {1, 2, 3, 4};
+  fxcrt::spancpy(vec.writable_span(), pdfium::make_span(kData));
+  EXPECT_THAT(vec.span(), testing::ElementsAre(1, 2, 3, 4));
+}
+
+TEST(FixedTryAllocZeroedDataVector, AllocFailure) {
+  constexpr size_t kCloseToMaxByteAlloc =
+      std::numeric_limits<size_t>::max() - 100;
+  FixedTryAllocZeroedDataVector<int> vec(kCloseToMaxByteAlloc);
+  EXPECT_TRUE(vec.empty());
+  EXPECT_EQ(0u, vec.size());
+  EXPECT_EQ(0u, vec.span().size());
+  EXPECT_EQ(0u, vec.writable_span().size());
+}
+
+TEST(FixedTryAllocZeroedDataVector, Move) {
+  FixedTryAllocZeroedDataVector<int> vec(4);
+  constexpr int kData[] = {1, 2, 3, 4};
+  ASSERT_EQ(4u, vec.writable_span().size());
+  fxcrt::spancpy(vec.writable_span(), pdfium::make_span(kData));
+  const int* const original_data_ptr = vec.span().data();
+
+  FixedTryAllocZeroedDataVector<int> vec2(std::move(vec));
+  EXPECT_FALSE(vec2.empty());
+  EXPECT_EQ(4u, vec2.size());
+  EXPECT_EQ(4u, vec2.span().size());
+  EXPECT_EQ(4u, vec2.writable_span().size());
+  EXPECT_THAT(vec2.span(), testing::ElementsAre(1, 2, 3, 4));
+  EXPECT_EQ(vec2.span().data(), original_data_ptr);
+
+  EXPECT_EQ(0u, vec.size());
+  EXPECT_TRUE(vec.empty());
+  EXPECT_TRUE(vec.span().empty());
+  EXPECT_TRUE(vec.writable_span().empty());
+
+  vec = std::move(vec2);
+  EXPECT_FALSE(vec.empty());
+  EXPECT_EQ(4u, vec.size());
+  EXPECT_EQ(4u, vec.span().size());
+  EXPECT_EQ(4u, vec.writable_span().size());
+  EXPECT_THAT(vec.span(), testing::ElementsAre(1, 2, 3, 4));
+  EXPECT_EQ(vec.span().data(), original_data_ptr);
+
+  EXPECT_EQ(0u, vec2.size());
+  EXPECT_TRUE(vec2.empty());
+  EXPECT_TRUE(vec2.span().empty());
+  EXPECT_TRUE(vec2.writable_span().empty());
+}
+
+TEST(FixedTryAllocZeroedDataVector, AssignFromFixedZeroedDataVector) {
+  FixedTryAllocZeroedDataVector<int> vec;
+
+  FixedZeroedDataVector<int> vec2(4);
+  constexpr int kData[] = {1, 2, 3, 4};
+  ASSERT_EQ(4u, vec2.writable_span().size());
+  fxcrt::spancpy(vec2.writable_span(), pdfium::make_span(kData));
+
+  vec = std::move(vec2);
+  EXPECT_TRUE(vec2.empty());
+  EXPECT_EQ(4u, vec.span().size());
+  EXPECT_THAT(vec.span(), testing::ElementsAre(1, 2, 3, 4));
+}
+
+TEST(FixedTryAllocZeroedDataVector, AssignFromFixedUninitDataVector) {
+  FixedTryAllocZeroedDataVector<int> vec;
+
+  FixedUninitDataVector<int> vec2(4);
+  constexpr int kData[] = {1, 2, 3, 4};
+  ASSERT_EQ(4u, vec2.writable_span().size());
+  fxcrt::spancpy(vec2.writable_span(), pdfium::make_span(kData));
+
+  vec = std::move(vec2);
+  EXPECT_TRUE(vec2.empty());
+  EXPECT_EQ(4u, vec.span().size());
+  EXPECT_THAT(vec.span(), testing::ElementsAre(1, 2, 3, 4));
+}
diff --git a/core/fxcrt/fixed_uninit_data_vector.h b/core/fxcrt/fixed_uninit_data_vector.h
index 420dc91..a208f5e 100644
--- a/core/fxcrt/fixed_uninit_data_vector.h
+++ b/core/fxcrt/fixed_uninit_data_vector.h
@@ -9,6 +9,6 @@
 
 template <typename T>
 using FixedUninitDataVector =
-    fxcrt::FixedSizeDataVector<T, /*initialize=*/false>;
+    fxcrt::FixedSizeDataVector<T, fxcrt::DataVectorAllocOption::kUninitialized>;
 
 #endif  // CORE_FXCRT_FIXED_UNINIT_DATA_VECTOR_H_
diff --git a/core/fxcrt/fixed_uninit_data_vector_unittest.cpp b/core/fxcrt/fixed_uninit_data_vector_unittest.cpp
index e874a95..d7a63da 100644
--- a/core/fxcrt/fixed_uninit_data_vector_unittest.cpp
+++ b/core/fxcrt/fixed_uninit_data_vector_unittest.cpp
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "core/fxcrt/fixed_try_alloc_zeroed_data_vector.h"
 #include "core/fxcrt/fixed_zeroed_data_vector.h"
 #include "core/fxcrt/span_util.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -37,6 +38,7 @@
   constexpr int kData[] = {1, 2, 3, 4};
   ASSERT_EQ(4u, vec.writable_span().size());
   fxcrt::spancpy(vec.writable_span(), pdfium::make_span(kData));
+  const int* const original_data_ptr = vec.span().data();
 
   FixedUninitDataVector<int> vec2(std::move(vec));
   EXPECT_FALSE(vec2.empty());
@@ -44,6 +46,7 @@
   EXPECT_EQ(4u, vec2.span().size());
   EXPECT_EQ(4u, vec2.writable_span().size());
   EXPECT_THAT(vec2.span(), testing::ElementsAre(1, 2, 3, 4));
+  EXPECT_EQ(vec2.span().data(), original_data_ptr);
 
   EXPECT_EQ(0u, vec.size());
   EXPECT_TRUE(vec.empty());
@@ -56,6 +59,7 @@
   EXPECT_EQ(4u, vec.span().size());
   EXPECT_EQ(4u, vec.writable_span().size());
   EXPECT_THAT(vec.span(), testing::ElementsAre(1, 2, 3, 4));
+  EXPECT_EQ(vec.span().data(), original_data_ptr);
 
   EXPECT_EQ(0u, vec2.size());
   EXPECT_TRUE(vec2.empty());
@@ -76,3 +80,17 @@
   EXPECT_EQ(4u, vec.span().size());
   EXPECT_THAT(vec.span(), testing::ElementsAre(1, 2, 3, 4));
 }
+
+TEST(FixedUninitDataVector, AssignFromFixedTryAllocZeroedDataVector) {
+  FixedUninitDataVector<int> vec;
+
+  FixedTryAllocZeroedDataVector<int> vec2(4);
+  constexpr int kData[] = {1, 2, 3, 4};
+  ASSERT_EQ(4u, vec2.writable_span().size());
+  fxcrt::spancpy(vec2.writable_span(), pdfium::make_span(kData));
+
+  vec = std::move(vec2);
+  EXPECT_TRUE(vec2.empty());
+  EXPECT_EQ(4u, vec.span().size());
+  EXPECT_THAT(vec.span(), testing::ElementsAre(1, 2, 3, 4));
+}
diff --git a/core/fxcrt/fixed_zeroed_data_vector.h b/core/fxcrt/fixed_zeroed_data_vector.h
index c1be086..c8b91a6 100644
--- a/core/fxcrt/fixed_zeroed_data_vector.h
+++ b/core/fxcrt/fixed_zeroed_data_vector.h
@@ -9,6 +9,6 @@
 
 template <typename T>
 using FixedZeroedDataVector =
-    fxcrt::FixedSizeDataVector<T, /*initialize=*/true>;
+    fxcrt::FixedSizeDataVector<T, fxcrt::DataVectorAllocOption::kInitialized>;
 
 #endif  // CORE_FXCRT_FIXED_ZEROED_DATA_VECTOR_H_
diff --git a/core/fxcrt/fixed_zeroed_data_vector_unittest.cpp b/core/fxcrt/fixed_zeroed_data_vector_unittest.cpp
index d23bfbc..ac024bb 100644
--- a/core/fxcrt/fixed_zeroed_data_vector_unittest.cpp
+++ b/core/fxcrt/fixed_zeroed_data_vector_unittest.cpp
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "core/fxcrt/fixed_try_alloc_zeroed_data_vector.h"
 #include "core/fxcrt/fixed_uninit_data_vector.h"
 #include "core/fxcrt/span_util.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -38,6 +39,7 @@
   constexpr int kData[] = {1, 2, 3, 4};
   ASSERT_EQ(4u, vec.writable_span().size());
   fxcrt::spancpy(vec.writable_span(), pdfium::make_span(kData));
+  const int* const original_data_ptr = vec.span().data();
 
   FixedZeroedDataVector<int> vec2(std::move(vec));
   EXPECT_FALSE(vec2.empty());
@@ -45,6 +47,7 @@
   EXPECT_EQ(4u, vec2.span().size());
   EXPECT_EQ(4u, vec2.writable_span().size());
   EXPECT_THAT(vec2.span(), testing::ElementsAre(1, 2, 3, 4));
+  EXPECT_EQ(vec2.span().data(), original_data_ptr);
 
   EXPECT_EQ(0u, vec.size());
   EXPECT_TRUE(vec.empty());
@@ -57,6 +60,7 @@
   EXPECT_EQ(4u, vec.span().size());
   EXPECT_EQ(4u, vec.writable_span().size());
   EXPECT_THAT(vec.span(), testing::ElementsAre(1, 2, 3, 4));
+  EXPECT_EQ(vec.span().data(), original_data_ptr);
 
   EXPECT_EQ(0u, vec2.size());
   EXPECT_TRUE(vec2.empty());
@@ -77,3 +81,17 @@
   EXPECT_EQ(4u, vec.span().size());
   EXPECT_THAT(vec.span(), testing::ElementsAre(1, 2, 3, 4));
 }
+
+TEST(FixedZeroedDataVector, AssignFromFixedTryAllocZeroedDataVector) {
+  FixedZeroedDataVector<int> vec;
+
+  FixedTryAllocZeroedDataVector<int> vec2(4);
+  constexpr int kData[] = {1, 2, 3, 4};
+  ASSERT_EQ(4u, vec2.writable_span().size());
+  fxcrt::spancpy(vec2.writable_span(), pdfium::make_span(kData));
+
+  vec = std::move(vec2);
+  EXPECT_TRUE(vec2.empty());
+  EXPECT_EQ(4u, vec.span().size());
+  EXPECT_THAT(vec.span(), testing::ElementsAre(1, 2, 3, 4));
+}