Combine FX_wcspos() and FX_strpos() into fxcrt::spanpos().

The code is nearly identical so make into a generic find span
within span function.

-- Add unit tests.

Change-Id: I154b102f70c7bfbf897c231e337636e8e9df4615
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/116470
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Thomas Sepez <tsepez@google.com>
diff --git a/core/fxcrt/bytestring.cpp b/core/fxcrt/bytestring.cpp
index 7d67743..83109fe 100644
--- a/core/fxcrt/bytestring.cpp
+++ b/core/fxcrt/bytestring.cpp
@@ -34,23 +34,6 @@
 
 constexpr char kTrimChars[] = "\x09\x0a\x0b\x0c\x0d\x20";
 
-std::optional<size_t> FX_strpos(pdfium::span<const char> haystack,
-                                pdfium::span<const char> needle) {
-  if (needle.empty() || needle.size() > haystack.size()) {
-    return std::nullopt;
-  }
-  // After `end_pos`, not enough characters remain in `haystack` for
-  // a full match to occur.
-  size_t end_pos = haystack.size() - needle.size();
-  for (size_t haystack_pos = 0; haystack_pos <= end_pos; ++haystack_pos) {
-    auto candidate = haystack.subspan(haystack_pos, needle.size());
-    if (fxcrt::span_equals(candidate, needle)) {
-      return haystack_pos;
-    }
-  }
-  return std::nullopt;
-}
-
 }  // namespace
 
 namespace fxcrt {
@@ -531,7 +514,7 @@
     return std::nullopt;
   }
   std::optional<size_t> result =
-      FX_strpos(m_pData->span().subspan(start), subStr.span());
+      spanpos(m_pData->span().subspan(start), subStr.span());
   if (!result.has_value()) {
     return std::nullopt;
   }
@@ -609,7 +592,7 @@
     // Limit span lifetime.
     pdfium::span<char> search_span = m_pData->span();
     while (true) {
-      std::optional<size_t> found = FX_strpos(search_span, pOld.span());
+      std::optional<size_t> found = spanpos(search_span, pOld.span());
       if (!found.has_value()) {
         break;
       }
@@ -634,12 +617,12 @@
     pdfium::span<const char> search_span = m_pData->span();
     pdfium::span<char> dest_span = pNewData->span();
     for (size_t i = 0; i < nCount; i++) {
-      size_t found = FX_strpos(search_span, pOld.span()).value();
-      dest_span = fxcrt::spancpy(dest_span, search_span.first(found));
-      dest_span = fxcrt::spancpy(dest_span, pNew.span());
+      size_t found = spanpos(search_span, pOld.span()).value();
+      dest_span = spancpy(dest_span, search_span.first(found));
+      dest_span = spancpy(dest_span, pNew.span());
       search_span = search_span.subspan(found + pOld.GetLength());
     }
-    dest_span = fxcrt::spancpy(dest_span, search_span);
+    dest_span = spancpy(dest_span, search_span);
     CHECK(dest_span.empty());
   }
   m_pData = std::move(pNewData);
diff --git a/core/fxcrt/span_util.h b/core/fxcrt/span_util.h
index 99ae5de..a88532a 100644
--- a/core/fxcrt/span_util.h
+++ b/core/fxcrt/span_util.h
@@ -7,6 +7,7 @@
 
 #include <stdint.h>
 
+#include <optional>
 #include <type_traits>
 
 #include "core/fxcrt/fx_memcpy_wrappers.h"
@@ -69,6 +70,29 @@
          FXSYS_memcmp(s1.data(), s2.data(), s1.size_bytes()) == 0;
 }
 
+// Returns the first position where `needle` occurs in `haystack`.
+template <typename T,
+          typename U,
+          typename = std::enable_if_t<sizeof(T) == sizeof(U) &&
+                                      std::is_trivially_copyable_v<T> &&
+                                      std::is_trivially_copyable_v<U>>>
+std::optional<size_t> spanpos(pdfium::span<T> haystack,
+                              pdfium::span<U> needle) {
+  if (needle.empty() || needle.size() > haystack.size()) {
+    return std::nullopt;
+  }
+  // After this `end_pos`, not enough characters remain in `haystack` for
+  // a full match to occur.
+  size_t end_pos = haystack.size() - needle.size();
+  for (size_t haystack_pos = 0; haystack_pos <= end_pos; ++haystack_pos) {
+    auto candidate = haystack.subspan(haystack_pos, needle.size());
+    if (fxcrt::span_equals(candidate, needle)) {
+      return haystack_pos;
+    }
+  }
+  return std::nullopt;
+}
+
 template <typename T,
           typename U,
           typename = typename std::enable_if_t<std::is_const_v<T> ||
diff --git a/core/fxcrt/span_util_unittest.cpp b/core/fxcrt/span_util_unittest.cpp
index 179c378..98250cc 100644
--- a/core/fxcrt/span_util_unittest.cpp
+++ b/core/fxcrt/span_util_unittest.cpp
@@ -6,6 +6,7 @@
 
 #include <vector>
 
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 TEST(Spanset, Fits) {
@@ -153,3 +154,30 @@
                    pdfium::make_span(abcabc).subspan(1, 4)),
                "");
 }
+
+TEST(Spanpos, Empty) {
+  pdfium::span<const uint32_t> kEmpty;
+  const uint32_t kHaystack[] = {0, 1, 2, 3, 4, 5};
+  const uint32_t kNeedle[] = {1, 2};
+  EXPECT_FALSE(fxcrt::spanpos(kEmpty, kEmpty));
+  EXPECT_FALSE(fxcrt::spanpos(pdfium::make_span(kHaystack), kEmpty));
+  EXPECT_FALSE(fxcrt::spanpos(kEmpty, pdfium::make_span(kNeedle)));
+}
+
+TEST(Spanpos, NotEmpty) {
+  const uint32_t kHaystack[] = {0, 1, 2, 3, 4, 5};
+  const uint32_t kStartMatch[] = {0, 1};
+  const uint32_t kEndMatch[] = {4, 5};
+  const uint32_t kNotFound[] = {256, 512};  // test byte-shifted {1,2}.
+  const uint32_t kTooLong[] = {0, 1, 2, 3, 4, 5, 6};
+  EXPECT_THAT(fxcrt::spanpos(pdfium::make_span(kHaystack),
+                             pdfium::make_span(kStartMatch)),
+              testing::Optional(0u));
+  EXPECT_THAT(fxcrt::spanpos(pdfium::make_span(kHaystack),
+                             pdfium::make_span(kEndMatch)),
+              testing::Optional(4u));
+  EXPECT_FALSE(fxcrt::spanpos(pdfium::make_span(kHaystack),
+                              pdfium::make_span(kNotFound)));
+  EXPECT_FALSE(fxcrt::spanpos(pdfium::make_span(kHaystack),
+                              pdfium::make_span(kTooLong)));
+}
diff --git a/core/fxcrt/widestring.cpp b/core/fxcrt/widestring.cpp
index d4155c0..a4bcdd8 100644
--- a/core/fxcrt/widestring.cpp
+++ b/core/fxcrt/widestring.cpp
@@ -54,23 +54,6 @@
 
 constexpr wchar_t kWideTrimChars[] = L"\x09\x0a\x0b\x0c\x0d\x20";
 
-std::optional<size_t> FX_wcspos(pdfium::span<const wchar_t> haystack,
-                                pdfium::span<const wchar_t> needle) {
-  if (needle.empty() || needle.size() > haystack.size()) {
-    return std::nullopt;
-  }
-  // After this `end_pos`, not enough characters remain in `haystack` for
-  // a full match to occur.
-  size_t end_pos = haystack.size() - needle.size();
-  for (size_t haystack_pos = 0; haystack_pos <= end_pos; ++haystack_pos) {
-    auto candidate = haystack.subspan(haystack_pos, needle.size());
-    if (fxcrt::span_equals(candidate, needle)) {
-      return haystack_pos;
-    }
-  }
-  return std::nullopt;
-}
-
 std::optional<size_t> GuessSizeForVSWPrintf(const wchar_t* pFormat,
                                             va_list argList) {
   size_t nMaxLen = 0;
@@ -847,7 +830,7 @@
     return std::nullopt;
   }
   std::optional<size_t> result =
-      FX_wcspos(m_pData->span().subspan(start), subStr.span());
+      spanpos(m_pData->span().subspan(start), subStr.span());
   if (!result.has_value()) {
     return std::nullopt;
   }
@@ -925,7 +908,7 @@
     // Limit span lifetime.
     pdfium::span<const wchar_t> search_span = m_pData->span();
     while (true) {
-      std::optional<size_t> found = FX_wcspos(search_span, pOld.span());
+      std::optional<size_t> found = spanpos(search_span, pOld.span());
       if (!found.has_value()) {
         break;
       }
@@ -950,12 +933,12 @@
     pdfium::span<const wchar_t> search_span = m_pData->span();
     pdfium::span<wchar_t> dest_span = pNewData->span();
     for (size_t i = 0; i < count; i++) {
-      size_t found = FX_wcspos(search_span, pOld.span()).value();
-      dest_span = fxcrt::spancpy(dest_span, search_span.first(found));
-      dest_span = fxcrt::spancpy(dest_span, pNew.span());
+      size_t found = spanpos(search_span, pOld.span()).value();
+      dest_span = spancpy(dest_span, search_span.first(found));
+      dest_span = spancpy(dest_span, pNew.span());
       search_span = search_span.subspan(found + pOld.GetLength());
     }
-    dest_span = fxcrt::spancpy(dest_span, search_span);
+    dest_span = spancpy(dest_span, search_span);
     CHECK(dest_span.empty());
   }
   m_pData = std::move(pNewData);