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));
+}