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