Fix potential 32-bit size_t overflow in SubstituteAndAppendArray()

PiperOrigin-RevId: 944682504
Change-Id: I26722efac261f27a1e9eabf7e13c62aa578364b7
diff --git a/absl/strings/substitute.cc b/absl/strings/substitute.cc
index f5d600b..244c8a5 100644
--- a/absl/strings/substitute.cc
+++ b/absl/strings/substitute.cc
@@ -20,6 +20,7 @@
 #include <cstdint>
 #include <limits>
 #include <string>
+#include <type_traits>
 
 #include "absl/base/config.h"
 #include "absl/base/internal/raw_logging.h"
@@ -35,6 +36,19 @@
 ABSL_NAMESPACE_BEGIN
 namespace substitute_internal {
 
+// Checked addition to avoid overflow on 32-bit. (In 64-bit the callers wouldn't
+// get anywhere close to the limit given that 2^64 bytes of memory is beyond
+// anything physically possible.)
+// Note that we don't declare the parameters as size_t in order to avoid
+// accidental implicit conversions.
+template <int&..., typename T>
+[[nodiscard]] std::enable_if_t<std::is_same_v<T, size_t>, T> CheckedAdd(T a,
+                                                                        T b) {
+  ABSL_INTERNAL_CHECK(b <= (std::numeric_limits<T>::max)() - a,
+                      "unsigned integer overflow");
+  return a + b;
+}
+
 void SubstituteAndAppendArray(std::string* absl_nonnull output,
                               absl::string_view format,
                               const absl::string_view* absl_nullable args_array,
@@ -64,10 +78,10 @@
 #endif
           return;
         }
-        size += args_array[index].size();
+        size = CheckedAdd(size, args_array[index].size());
         ++i;  // Skip next char.
       } else if (format[i + 1] == '$') {
-        ++size;
+        size = CheckedAdd(size, size_t{1});
         ++i;  // Skip next char.
       } else {
 #ifndef NDEBUG
@@ -78,7 +92,7 @@
         return;
       }
     } else {
-      ++size;
+      size = CheckedAdd(size, size_t{1});
     }
   }
 
diff --git a/absl/strings/substitute_test.cc b/absl/strings/substitute_test.cc
index 70f9119..e62a89a 100644
--- a/absl/strings/substitute_test.cc
+++ b/absl/strings/substitute_test.cc
@@ -16,6 +16,7 @@
 
 #include <cstdint>
 #include <cstring>
+#include <limits>
 #include <string>
 #include <vector>
 
@@ -283,6 +284,17 @@
       "Invalid absl::Substitute\\(\\) format string: \"-\\$\"");
 }
 
+TEST(SubstituteDeathTest, OverflowDeath) {
+  // HACK: We pretend the string_view is extremely long, in order to test an
+  // overflow condition that only occurs in 32-bit without using an impractical
+  // amount of memory.
+  EXPECT_DEATH(static_cast<void>(absl::Substitute(
+                   "$0$0$0",
+                   absl::string_view(
+                       "abc", (std::numeric_limits<size_t>::max() / 3) + 100))),
+               "overflow");
+}
+
 #endif  // GTEST_HAS_DEATH_TEST
 
 }  // namespace