Introduce fxcrt::try_spancpy() and fxcrt::try_spanmove().
Avoid the possibility of getting the test wrong for this very
common pattern.
Change-Id: I633c99e7a1b3f0198b27534e58bec87134e6deb8
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/118294
Reviewed-by: Thomas Sepez <tsepez@google.com>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/core/fxcrt/span_util.h b/core/fxcrt/span_util.h
index daf2c0c..4dedfe8 100644
--- a/core/fxcrt/span_util.h
+++ b/core/fxcrt/span_util.h
@@ -48,8 +48,8 @@
typename = std::enable_if_t<sizeof(T1) == sizeof(T2) &&
std::is_trivially_copyable_v<T1> &&
std::is_trivially_copyable_v<T2>>>
-pdfium::span<T1> spanmove(pdfium::span<T1, N1, P1> dst,
- pdfium::span<T2, N2, P2> src) {
+inline pdfium::span<T1> spanmove(pdfium::span<T1, N1, P1> dst,
+ pdfium::span<T2, N2, P2> src) {
CHECK_GE(dst.size(), src.size());
// SAFETY: SFINAE ensures `sizeof(T1)` equals `sizeof(T2)`, so comparing
// `size()` for equality ensures `size_bytes()` are equal, and `size_bytes()`
@@ -58,6 +58,54 @@
return dst.subspan(src.size());
}
+// Bounds-checked byte-for-byte copies from spans into spans. Performs the
+// copy if there is room, and returns true. Otherwise does not copy anything
+// and returns false.
+template <typename T1,
+ typename T2,
+ size_t N1,
+ size_t N2,
+ typename P1,
+ typename P2,
+ typename = std::enable_if_t<sizeof(T1) == sizeof(T2) &&
+ std::is_trivially_copyable_v<T1> &&
+ std::is_trivially_copyable_v<T2>>>
+inline bool try_spancpy(pdfium::span<T1, N1, P1> dst,
+ pdfium::span<T2, N2, P2> src) {
+ if (dst.size() < src.size()) {
+ return false;
+ }
+ // SAFETY: SFINAE ensures `sizeof(T1)` equals `sizeof(T2)`, the test above
+ // ensures `src.size()` <= `dst.size()` which implies `src.size_bytes()`
+ // <= `dst.size_bytes()`, and `dst.size_bytes()` describes `dst.data()`.
+ UNSAFE_BUFFERS(FXSYS_memcpy(dst.data(), src.data(), src.size_bytes()));
+ return true;
+}
+
+// Bounds-checked byte-for-byte moves from spans into spans. Peforms the
+// move if there is room, and returns true. Otherwise does not move anything
+// and returns false.
+template <typename T1,
+ typename T2,
+ size_t N1,
+ size_t N2,
+ typename P1,
+ typename P2,
+ typename = std::enable_if_t<sizeof(T1) == sizeof(T2) &&
+ std::is_trivially_copyable_v<T1> &&
+ std::is_trivially_copyable_v<T2>>>
+inline bool try_spanmove(pdfium::span<T1, N1, P1> dst,
+ pdfium::span<T2, N2, P2> src) {
+ if (dst.size() < src.size()) {
+ return false;
+ }
+ // SAFETY: SFINAE ensures `sizeof(T1)` equals `sizeof(T2)`, the test above
+ // ensures `src.size()` <= `dst.size()` which implies `src.size_bytes()`
+ // <= `dst.size_bytes()`, and `dst.size_bytes()` describes `dst.data()`.
+ UNSAFE_BUFFERS(FXSYS_memmove(dst.data(), src.data(), src.size_bytes()));
+ return true;
+}
+
// Bounds-checked sets into spans.
template <typename T,
size_t N,
diff --git a/core/fxcrt/span_util_unittest.cpp b/core/fxcrt/span_util_unittest.cpp
index 98250cc..825bf2b 100644
--- a/core/fxcrt/span_util_unittest.cpp
+++ b/core/fxcrt/span_util_unittest.cpp
@@ -77,6 +77,27 @@
EXPECT_TRUE(remain.empty());
}
+TEST(Spancpy, TryFitsEntirely) {
+ std::vector<char> src(4, 'A');
+ std::vector<char> dst(4, 'B');
+ EXPECT_TRUE(
+ fxcrt::try_spancpy(pdfium::make_span(dst), pdfium::make_span(src)));
+ EXPECT_EQ(dst[0], 'A');
+ EXPECT_EQ(dst[1], 'A');
+ EXPECT_EQ(dst[2], 'A');
+ EXPECT_EQ(dst[3], 'A');
+}
+
+TEST(Spancpy, TryDoesNotFit) {
+ std::vector<char> src(4, 'A');
+ std::vector<char> dst(3, 'B');
+ EXPECT_FALSE(
+ fxcrt::try_spancpy(pdfium::make_span(dst), pdfium::make_span(src)));
+ EXPECT_EQ(dst[0], 'B');
+ EXPECT_EQ(dst[1], 'B');
+ EXPECT_EQ(dst[2], 'B');
+}
+
TEST(Spanmove, FitsWithin) {
std::vector<char> src(2, 'A');
std::vector<char> dst(4, 'B');
@@ -91,6 +112,48 @@
EXPECT_EQ(remain.data(), &dst[3]);
}
+TEST(Spanmove, TryFitsEntirely) {
+ std::vector<char> src(4, 'A');
+ std::vector<char> dst(4, 'B');
+ EXPECT_TRUE(
+ fxcrt::try_spanmove(pdfium::make_span(dst), pdfium::make_span(src)));
+ EXPECT_EQ(dst[0], 'A');
+ EXPECT_EQ(dst[1], 'A');
+ EXPECT_EQ(dst[2], 'A');
+ EXPECT_EQ(dst[3], 'A');
+}
+
+TEST(Spanmove, TrySelfIntersect) {
+ {
+ std::vector<char> vec = {'A', 'B', 'C', 'D'};
+ EXPECT_TRUE(fxcrt::try_spanmove(pdfium::make_span(vec).first(3),
+ pdfium::make_span(vec).last(3)));
+ EXPECT_EQ(vec[0], 'B');
+ EXPECT_EQ(vec[1], 'C');
+ EXPECT_EQ(vec[2], 'D');
+ EXPECT_EQ(vec[3], 'D');
+ }
+ {
+ std::vector<char> vec = {'A', 'B', 'C', 'D'};
+ EXPECT_TRUE(fxcrt::try_spanmove(pdfium::make_span(vec).last(3),
+ pdfium::make_span(vec).first(3)));
+ EXPECT_EQ(vec[0], 'A');
+ EXPECT_EQ(vec[1], 'A');
+ EXPECT_EQ(vec[2], 'B');
+ EXPECT_EQ(vec[3], 'C');
+ }
+}
+
+TEST(Spanmove, TryDoesNotFit) {
+ std::vector<char> src(4, 'A');
+ std::vector<char> dst(3, 'B');
+ EXPECT_FALSE(
+ fxcrt::try_spanmove(pdfium::make_span(dst), pdfium::make_span(src)));
+ EXPECT_EQ(dst[0], 'B');
+ EXPECT_EQ(dst[1], 'B');
+ EXPECT_EQ(dst[2], 'B');
+}
+
TEST(SpanEquals, Empty) {
std::vector<int> vec = {1, 2};
std::vector<int> vec2 = {3, 4};