Place common code for byte/wide strings in StringTemplate.

Initially, the bytestring and widestring code was quite divergent,
but over time portions of it have become identical apart from type.

-- Remove one duplicate instantiation of extern template class.

Change-Id: I0f42909b0f597fe05269e729c0233910721267a5
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/116410
Reviewed-by: Thomas Sepez <tsepez@google.com>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
diff --git a/core/fxcrt/BUILD.gn b/core/fxcrt/BUILD.gn
index ddebda5..c05196f 100644
--- a/core/fxcrt/BUILD.gn
+++ b/core/fxcrt/BUILD.gn
@@ -90,6 +90,8 @@
     "string_data_template.cpp",
     "string_data_template.h",
     "string_pool_template.h",
+    "string_template.cpp",
+    "string_template.h",
     "string_view_template.h",
     "tree_node.h",
     "unowned_ptr.h",
diff --git a/core/fxcrt/bytestring.cpp b/core/fxcrt/bytestring.cpp
index 83109fe..bff4b3c 100644
--- a/core/fxcrt/bytestring.cpp
+++ b/core/fxcrt/bytestring.cpp
@@ -25,7 +25,7 @@
 #include "third_party/base/check_op.h"
 #include "third_party/base/containers/span.h"
 
-template class fxcrt::StringDataTemplate<char>;
+// Instantiate.
 template class fxcrt::StringViewTemplate<char>;
 template class fxcrt::StringPoolTemplate<ByteString>;
 template struct std::hash<ByteString>;
@@ -159,14 +159,6 @@
 
 ByteString::~ByteString() = default;
 
-void ByteString::clear() {
-  if (m_pData && m_pData->CanOperateInPlace(0)) {
-    m_pData->m_nDataLength = 0;
-    return;
-  }
-  m_pData.Reset();
-}
-
 ByteString& ByteString::operator=(const char* str) {
   if (!str || !str[0])
     clear();
@@ -310,44 +302,6 @@
   return true;
 }
 
-void ByteString::AssignCopy(const char* pSrcData, size_t nSrcLen) {
-  AllocBeforeWrite(nSrcLen);
-  m_pData->CopyContents({pSrcData, nSrcLen});
-  m_pData->m_nDataLength = nSrcLen;
-}
-
-void ByteString::ReallocBeforeWrite(size_t nNewLength) {
-  if (m_pData && m_pData->CanOperateInPlace(nNewLength))
-    return;
-
-  if (nNewLength == 0) {
-    clear();
-    return;
-  }
-
-  RetainPtr<StringData> pNewData = StringData::Create(nNewLength);
-  if (m_pData) {
-    size_t nCopyLength = std::min(m_pData->m_nDataLength, nNewLength);
-    pNewData->CopyContents({m_pData->m_String, nCopyLength});
-    pNewData->m_nDataLength = nCopyLength;
-  } else {
-    pNewData->m_nDataLength = 0;
-  }
-  pNewData->m_String[pNewData->m_nDataLength] = 0;
-  m_pData = std::move(pNewData);
-}
-
-void ByteString::AllocBeforeWrite(size_t nNewLength) {
-  if (m_pData && m_pData->CanOperateInPlace(nNewLength)) {
-    return;
-  }
-  if (nNewLength == 0) {
-    clear();
-    return;
-  }
-  m_pData = StringData::Create(nNewLength);
-}
-
 void ByteString::ReleaseBuffer(size_t nNewLength) {
   if (!m_pData)
     return;
@@ -419,30 +373,6 @@
   return m_pData->m_nDataLength;
 }
 
-void ByteString::Concat(const char* pSrcData, size_t nSrcLen) {
-  if (!pSrcData || nSrcLen == 0)
-    return;
-
-  if (!m_pData) {
-    m_pData = StringData::Create({pSrcData, nSrcLen});
-    return;
-  }
-
-  if (m_pData->CanOperateInPlace(m_pData->m_nDataLength + nSrcLen)) {
-    m_pData->CopyContentsAt(m_pData->m_nDataLength, {pSrcData, nSrcLen});
-    m_pData->m_nDataLength += nSrcLen;
-    return;
-  }
-
-  size_t nConcatLen = std::max(m_pData->m_nDataLength / 2, nSrcLen);
-  RetainPtr<StringData> pNewData =
-      StringData::Create(m_pData->m_nDataLength + nConcatLen);
-  pNewData->CopyContents(*m_pData);
-  pNewData->CopyContentsAt(m_pData->m_nDataLength, {pSrcData, nSrcLen});
-  pNewData->m_nDataLength = m_pData->m_nDataLength + nSrcLen;
-  m_pData = std::move(pNewData);
-}
-
 intptr_t ByteString::ReferenceCountForTesting() const {
   return m_pData ? m_pData->m_nRefs : 0;
 }
diff --git a/core/fxcrt/bytestring.h b/core/fxcrt/bytestring.h
index f9ace48..a5a33dc 100644
--- a/core/fxcrt/bytestring.h
+++ b/core/fxcrt/bytestring.h
@@ -21,6 +21,7 @@
 #include "core/fxcrt/fx_string_wrappers.h"
 #include "core/fxcrt/retain_ptr.h"
 #include "core/fxcrt/string_data_template.h"
+#include "core/fxcrt/string_template.h"
 #include "core/fxcrt/string_view_template.h"
 #include "third_party/base/check.h"
 #include "third_party/base/containers/span.h"
@@ -29,12 +30,8 @@
 
 // A mutable string with shared buffers using copy-on-write semantics that
 // avoids the cost of std::string's iterator stability guarantees.
-class ByteString {
+class ByteString : public StringTemplate<char> {
  public:
-  using CharType = char;
-  using const_iterator = const CharType*;
-  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
-
   [[nodiscard]] static ByteString FormatInteger(int i);
   [[nodiscard]] static ByteString FormatFloat(float f);
   [[nodiscard]] static ByteString Format(const char* pFormat, ...);
@@ -67,10 +64,6 @@
 
   ~ByteString();
 
-  // Holds on to buffer if possible for later re-use. Assign ByteString()
-  // to force immediate release if desired.
-  void clear();
-
   // Explicit conversion to C-style string. The result is never nullptr,
   // and is always NUL terminated.
   // Note: Any subsequent modification of |this| will invalidate the result.
@@ -222,16 +215,8 @@
   uint32_t GetID() const { return AsStringView().GetID(); }
 
  protected:
-  using StringData = StringDataTemplate<char>;
-
-  void ReallocBeforeWrite(size_t nNewLen);
-  void AllocBeforeWrite(size_t nNewLen);
-  void AssignCopy(const char* pSrcData, size_t nSrcLen);
-  void Concat(const char* pSrcData, size_t nSrcLen);
   intptr_t ReferenceCountForTesting() const;
 
-  RetainPtr<StringData> m_pData;
-
   friend class ByteString_Assign_Test;
   friend class ByteString_Concat_Test;
   friend class ByteString_Construct_Test;
diff --git a/core/fxcrt/string_data_template.cpp b/core/fxcrt/string_data_template.cpp
index fb5c3c4..8b418a5 100644
--- a/core/fxcrt/string_data_template.cpp
+++ b/core/fxcrt/string_data_template.cpp
@@ -91,6 +91,7 @@
   m_String[dataLen] = 0;
 }
 
+// Instantiate.
 template class StringDataTemplate<char>;
 template class StringDataTemplate<wchar_t>;
 
diff --git a/core/fxcrt/string_template.cpp b/core/fxcrt/string_template.cpp
new file mode 100644
index 0000000..85fd480
--- /dev/null
+++ b/core/fxcrt/string_template.cpp
@@ -0,0 +1,99 @@
+// Copyright 2024 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
+
+#include "core/fxcrt/string_template.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "core/fxcrt/span_util.h"
+#include "third_party/base/check.h"
+#include "third_party/base/check_op.h"
+#include "third_party/base/containers/span.h"
+
+namespace fxcrt {
+
+template <typename T>
+void StringTemplate<T>::ReallocBeforeWrite(size_t nNewLength) {
+  if (m_pData && m_pData->CanOperateInPlace(nNewLength)) {
+    return;
+  }
+  if (nNewLength == 0) {
+    clear();
+    return;
+  }
+
+  RetainPtr<StringData> pNewData = StringData::Create(nNewLength);
+  if (m_pData) {
+    size_t nCopyLength = std::min(m_pData->m_nDataLength, nNewLength);
+    pNewData->CopyContents({m_pData->m_String, nCopyLength});
+    pNewData->m_nDataLength = nCopyLength;
+  } else {
+    pNewData->m_nDataLength = 0;
+  }
+  pNewData->m_String[pNewData->m_nDataLength] = 0;
+  m_pData = std::move(pNewData);
+}
+
+template <typename T>
+void StringTemplate<T>::AllocBeforeWrite(size_t nNewLength) {
+  if (m_pData && m_pData->CanOperateInPlace(nNewLength)) {
+    return;
+  }
+  if (nNewLength == 0) {
+    clear();
+    return;
+  }
+  m_pData = StringData::Create(nNewLength);
+}
+
+template <typename T>
+void StringTemplate<T>::AssignCopy(const T* pSrcData, size_t nSrcLen) {
+  AllocBeforeWrite(nSrcLen);
+  m_pData->CopyContents({pSrcData, nSrcLen});
+  m_pData->m_nDataLength = nSrcLen;
+}
+
+template <typename T>
+void StringTemplate<T>::Concat(const T* pSrcData, size_t nSrcLen) {
+  if (!pSrcData || nSrcLen == 0) {
+    return;
+  }
+
+  if (!m_pData) {
+    m_pData = StringData::Create({pSrcData, nSrcLen});
+    return;
+  }
+
+  if (m_pData->CanOperateInPlace(m_pData->m_nDataLength + nSrcLen)) {
+    m_pData->CopyContentsAt(m_pData->m_nDataLength, {pSrcData, nSrcLen});
+    m_pData->m_nDataLength += nSrcLen;
+    return;
+  }
+
+  size_t nConcatLen = std::max(m_pData->m_nDataLength / 2, nSrcLen);
+  RetainPtr<StringData> pNewData =
+      StringData::Create(m_pData->m_nDataLength + nConcatLen);
+  pNewData->CopyContents(*m_pData);
+  pNewData->CopyContentsAt(m_pData->m_nDataLength, {pSrcData, nSrcLen});
+  pNewData->m_nDataLength = m_pData->m_nDataLength + nSrcLen;
+  m_pData = std::move(pNewData);
+}
+
+template <typename T>
+void StringTemplate<T>::clear() {
+  if (m_pData && m_pData->CanOperateInPlace(0)) {
+    m_pData->m_nDataLength = 0;
+    return;
+  }
+  m_pData.Reset();
+}
+
+// Instantiate.
+template class StringTemplate<char>;
+template class StringTemplate<wchar_t>;
+
+}  // namespace fxcrt
diff --git a/core/fxcrt/string_template.h b/core/fxcrt/string_template.h
new file mode 100644
index 0000000..51be807
--- /dev/null
+++ b/core/fxcrt/string_template.h
@@ -0,0 +1,48 @@
+// Copyright 2024 The PDFium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
+
+#ifndef CORE_FXCRT_STRING_TEMPLATE_H_
+#define CORE_FXCRT_STRING_TEMPLATE_H_
+
+#include <stddef.h>
+
+#include "core/fxcrt/retain_ptr.h"
+#include "core/fxcrt/string_data_template.h"
+#include "core/fxcrt/string_view_template.h"
+
+namespace fxcrt {
+
+// Base class for a  mutable string with shared buffers using copy-on-write
+// semantics that avoids std::string's iterator stability guarantees.
+template <typename T>
+class StringTemplate {
+ public:
+  using CharType = T;
+  using const_iterator = T*;
+  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+  // Holds on to buffer if possible for later re-use. Use assignment
+  // to force immediate release if desired.
+  void clear();
+
+ protected:
+  using StringView = StringViewTemplate<T>;
+  using StringData = StringDataTemplate<T>;
+
+  void ReallocBeforeWrite(size_t nNewLen);
+  void AllocBeforeWrite(size_t nNewLen);
+  void AssignCopy(const T* pSrcData, size_t nSrcLen);
+  void Concat(const T* pSrcData, size_t nSrcLen);
+
+  RetainPtr<StringData> m_pData;
+};
+
+extern template class StringTemplate<char>;
+extern template class StringTemplate<wchar_t>;
+
+}  // namespace fxcrt
+
+#endif  // CORE_FXCRT_STRING_TEMPLATE_H_
diff --git a/core/fxcrt/widestring.cpp b/core/fxcrt/widestring.cpp
index a4bcdd8..a9cd10b 100644
--- a/core/fxcrt/widestring.cpp
+++ b/core/fxcrt/widestring.cpp
@@ -24,7 +24,7 @@
 #include "third_party/base/check_op.h"
 #include "third_party/base/numerics/safe_math.h"
 
-template class fxcrt::StringDataTemplate<wchar_t>;
+// Instantiate.
 template class fxcrt::StringViewTemplate<wchar_t>;
 template class fxcrt::StringPoolTemplate<WideString>;
 template struct std::hash<WideString>;
@@ -443,14 +443,6 @@
 
 WideString::~WideString() = default;
 
-void WideString::clear() {
-  if (m_pData && m_pData->CanOperateInPlace(0)) {
-    m_pData->m_nDataLength = 0;
-    return;
-  }
-  m_pData.Reset();
-}
-
 WideString& WideString::operator=(const wchar_t* str) {
   if (!str || !str[0])
     clear();
@@ -565,45 +557,6 @@
   return Compare(other) < 0;
 }
 
-void WideString::AssignCopy(const wchar_t* pSrcData, size_t nSrcLen) {
-  AllocBeforeWrite(nSrcLen);
-  m_pData->CopyContents({pSrcData, nSrcLen});
-  m_pData->m_nDataLength = nSrcLen;
-}
-
-void WideString::ReallocBeforeWrite(size_t nNewLength) {
-  if (m_pData && m_pData->CanOperateInPlace(nNewLength))
-    return;
-
-  if (nNewLength == 0) {
-    clear();
-    return;
-  }
-
-  RetainPtr<StringData> pNewData = StringData::Create(nNewLength);
-  if (m_pData) {
-    size_t nCopyLength = std::min(m_pData->m_nDataLength, nNewLength);
-    pNewData->CopyContents({m_pData->m_String, nCopyLength});
-    pNewData->m_nDataLength = nCopyLength;
-  } else {
-    pNewData->m_nDataLength = 0;
-  }
-  pNewData->m_String[pNewData->m_nDataLength] = 0;
-  m_pData = std::move(pNewData);
-}
-
-void WideString::AllocBeforeWrite(size_t nNewLength) {
-  if (m_pData && m_pData->CanOperateInPlace(nNewLength))
-    return;
-
-  if (nNewLength == 0) {
-    clear();
-    return;
-  }
-
-  m_pData = StringData::Create(nNewLength);
-}
-
 void WideString::ReleaseBuffer(size_t nNewLength) {
   if (!m_pData)
     return;
@@ -675,30 +628,6 @@
   return m_pData->m_nDataLength;
 }
 
-void WideString::Concat(const wchar_t* pSrcData, size_t nSrcLen) {
-  if (!pSrcData || nSrcLen == 0)
-    return;
-
-  if (!m_pData) {
-    m_pData = StringData::Create({pSrcData, nSrcLen});
-    return;
-  }
-
-  if (m_pData->CanOperateInPlace(m_pData->m_nDataLength + nSrcLen)) {
-    m_pData->CopyContentsAt(m_pData->m_nDataLength, {pSrcData, nSrcLen});
-    m_pData->m_nDataLength += nSrcLen;
-    return;
-  }
-
-  size_t nConcatLen = std::max(m_pData->m_nDataLength / 2, nSrcLen);
-  RetainPtr<StringData> pNewData =
-      StringData::Create(m_pData->m_nDataLength + nConcatLen);
-  pNewData->CopyContents(*m_pData);
-  pNewData->CopyContentsAt(m_pData->m_nDataLength, {pSrcData, nSrcLen});
-  pNewData->m_nDataLength = m_pData->m_nDataLength + nSrcLen;
-  m_pData = std::move(pNewData);
-}
-
 intptr_t WideString::ReferenceCountForTesting() const {
   return m_pData ? m_pData->m_nRefs : 0;
 }
diff --git a/core/fxcrt/widestring.h b/core/fxcrt/widestring.h
index ae79e11..3d199fd 100644
--- a/core/fxcrt/widestring.h
+++ b/core/fxcrt/widestring.h
@@ -20,6 +20,7 @@
 
 #include "core/fxcrt/retain_ptr.h"
 #include "core/fxcrt/string_data_template.h"
+#include "core/fxcrt/string_template.h"
 #include "core/fxcrt/string_view_template.h"
 #include "third_party/base/check.h"
 #include "third_party/base/containers/span.h"
@@ -30,13 +31,9 @@
 
 // A mutable string with shared buffers using copy-on-write semantics that
 // avoids the cost of std::string's iterator stability guarantees.
-class WideString {
+// TODO(crbug.com/pdfium/2031): Consider switching to `char16_t` instead.
+class WideString : public StringTemplate<wchar_t> {
  public:
-  // TODO(crbug.com/pdfium/2031): Consider switching to `char16_t` instead.
-  using CharType = wchar_t;
-  using const_iterator = const CharType*;
-  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
-
   [[nodiscard]] static WideString FormatInteger(int i);
   [[nodiscard]] static WideString Format(const wchar_t* pFormat, ...);
   [[nodiscard]] static WideString FormatV(const wchar_t* lpszFormat,
@@ -108,10 +105,6 @@
     return const_reverse_iterator(begin());
   }
 
-  // Holds on to buffer if possible for later re-use. Assign WideString()
-  // to force immediate release if desired.
-  void clear();
-
   size_t GetLength() const { return m_pData ? m_pData->m_nDataLength : 0; }
   size_t GetStringLength() const {
     return m_pData ? wcslen(m_pData->m_String) : 0;
@@ -241,16 +234,8 @@
   WideString EncodeEntities() const;
 
  protected:
-  using StringData = StringDataTemplate<wchar_t>;
-
-  void ReallocBeforeWrite(size_t nNewLength);
-  void AllocBeforeWrite(size_t nNewLength);
-  void AssignCopy(const wchar_t* pSrcData, size_t nSrcLen);
-  void Concat(const wchar_t* pSrcData, size_t nSrcLen);
   intptr_t ReferenceCountForTesting() const;
 
-  RetainPtr<StringData> m_pData;
-
   friend class WideString_Assign_Test;
   friend class WideString_ConcatInPlace_Test;
   friend class WideString_Construct_Test;