[libc-commits] [libc] [llvm] [libc] Don't touch str_end in strto* and wcsto* functions when base is incorrect (PR #200073)

Alexey Samsonov via libc-commits libc-commits at lists.llvm.org
Wed May 27 23:03:53 PDT 2026


https://github.com/vonosmas updated https://github.com/llvm/llvm-project/pull/200073

>From 1ea407ed95e338da52546b7d59139d92e41217cc Mon Sep 17 00:00:00 2001
From: Alexey Samsonov <vonosmas at gmail.com>
Date: Wed, 27 May 2026 21:54:29 +0000
Subject: [PATCH 1/6] Fix EINVAL handling in strto*/wcsto* functions.

---
 libc/src/inttypes/CMakeLists.txt         |  6 ++--
 libc/src/inttypes/strtoimax.cpp          | 13 ++-----
 libc/src/inttypes/strtoumax.cpp          | 13 ++-----
 libc/src/stdlib/CMakeLists.txt           | 43 ++++++++++++------------
 libc/src/stdlib/atoi.cpp                 | 11 ++----
 libc/src/stdlib/atol.cpp                 | 10 ++----
 libc/src/stdlib/atoll.cpp                | 10 ++----
 libc/src/stdlib/str_to_util.h            | 41 ++++++++++++++++++++++
 libc/src/stdlib/strtol.cpp               | 13 ++-----
 libc/src/stdlib/strtol_l.cpp             | 13 ++-----
 libc/src/stdlib/strtoll.cpp              | 13 ++-----
 libc/src/stdlib/strtoll_l.cpp            | 13 ++-----
 libc/src/stdlib/strtoul.cpp              | 13 ++-----
 libc/src/stdlib/strtoul_l.cpp            | 13 ++-----
 libc/src/stdlib/strtoull.cpp             | 13 ++-----
 libc/src/stdlib/strtoull_l.cpp           | 13 ++-----
 libc/src/wchar/CMakeLists.txt            | 12 +++----
 libc/src/wchar/wcstol.cpp                | 13 ++-----
 libc/src/wchar/wcstoll.cpp               | 13 ++-----
 libc/src/wchar/wcstoul.cpp               | 13 ++-----
 libc/src/wchar/wcstoull.cpp              | 13 ++-----
 libc/test/src/stdlib/CMakeLists.txt      |  6 ++--
 libc/test/src/stdlib/StrtolTest.h        |  5 ++-
 libc/test/src/stdlib/strtoint32_test.cpp | 21 ++----------
 libc/test/src/stdlib/strtoint64_test.cpp | 21 ++----------
 libc/test/src/wchar/WcstolTest.h         |  5 ++-
 26 files changed, 119 insertions(+), 254 deletions(-)
 create mode 100644 libc/src/stdlib/str_to_util.h

diff --git a/libc/src/inttypes/CMakeLists.txt b/libc/src/inttypes/CMakeLists.txt
index 3a48c9a1a8b97..eb9f2f3375eab 100644
--- a/libc/src/inttypes/CMakeLists.txt
+++ b/libc/src/inttypes/CMakeLists.txt
@@ -6,8 +6,7 @@ add_entrypoint_object(
     strtoimax.h
   DEPENDS
     libc.hdr.stdint_proxy
-    libc.src.__support.str_to_integer
-    libc.src.errno.errno
+    libc.src.stdlib.str_to_util
 )
 
 add_entrypoint_object(
@@ -18,8 +17,7 @@ add_entrypoint_object(
     strtoumax.h
   DEPENDS
     libc.hdr.stdint_proxy
-    libc.src.__support.str_to_integer
-    libc.src.errno.errno
+    libc.src.stdlib.str_to_util
 )
 
 add_entrypoint_object(
diff --git a/libc/src/inttypes/strtoimax.cpp b/libc/src/inttypes/strtoimax.cpp
index 6e55a4b56aac7..213d8800aab80 100644
--- a/libc/src/inttypes/strtoimax.cpp
+++ b/libc/src/inttypes/strtoimax.cpp
@@ -7,24 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/inttypes/strtoimax.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(intmax_t, strtoimax,
                    (const char *__restrict str, char **__restrict str_end,
                     int base)) {
-  auto result = internal::strtointeger<intmax_t>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<char *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<intmax_t>(str, str_end, base);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/inttypes/strtoumax.cpp b/libc/src/inttypes/strtoumax.cpp
index ce5a0a782d979..6191ad0491bf2 100644
--- a/libc/src/inttypes/strtoumax.cpp
+++ b/libc/src/inttypes/strtoumax.cpp
@@ -7,24 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/inttypes/strtoumax.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(uintmax_t, strtoumax,
                    (const char *__restrict str, char **__restrict str_end,
                     int base)) {
-  auto result = internal::strtointeger<uintmax_t>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<char *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<uintmax_t>(str, str_end, base);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 7e542e56a983d..2299bc1f0b0c5 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -5,8 +5,7 @@ add_entrypoint_object(
   HDRS
     atoi.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    .str_to_util
 )
 
 add_entrypoint_object(
@@ -27,8 +26,7 @@ add_entrypoint_object(
   HDRS
     atol.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    .str_to_util
 )
 
 add_entrypoint_object(
@@ -38,8 +36,7 @@ add_entrypoint_object(
   HDRS
     atoll.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    .str_to_util
 )
 
 add_entrypoint_object(
@@ -144,6 +141,16 @@ add_header_library(
     libc.src.__support.CPP.type_traits
 )
 
+add_header_library(
+  str_to_util
+  HDRS
+    str_to_util.h
+  DEPENDS
+    libc.src.__support.common
+    libc.src.__support.libc_errno
+    libc.src.__support.str_to_integer
+)
+
 add_entrypoint_object(
   strtof
   SRCS
@@ -184,8 +191,7 @@ add_entrypoint_object(
   HDRS
     strtol.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    .str_to_util
 )
 
 add_entrypoint_object(
@@ -195,8 +201,7 @@ add_entrypoint_object(
   HDRS
     strtoll.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    .str_to_util
 )
 
 add_entrypoint_object(
@@ -206,8 +211,7 @@ add_entrypoint_object(
   HDRS
     strtoul.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    .str_to_util
 )
 
 add_entrypoint_object(
@@ -217,8 +221,7 @@ add_entrypoint_object(
   HDRS
     strtoull.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    .str_to_util
 )
 
 add_entrypoint_object(
@@ -612,8 +615,7 @@ add_entrypoint_object(
   HDRS
     strtol_l.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    .str_to_util
 )
 
 add_entrypoint_object(
@@ -623,8 +625,7 @@ add_entrypoint_object(
   HDRS
     strtoll_l.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    .str_to_util
 )
 
 add_entrypoint_object(
@@ -634,8 +635,7 @@ add_entrypoint_object(
   HDRS
     strtoul_l.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    .str_to_util
 )
 
 add_entrypoint_object(
@@ -645,8 +645,7 @@ add_entrypoint_object(
   HDRS
     strtoull_l.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    .str_to_util
 )
 
 if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
diff --git a/libc/src/stdlib/atoi.cpp b/libc/src/stdlib/atoi.cpp
index 420bbc8143d55..9b030685a603b 100644
--- a/libc/src/stdlib/atoi.cpp
+++ b/libc/src/stdlib/atoi.cpp
@@ -7,21 +7,16 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/atoi.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(int, atoi, (const char *str)) {
   // This is done because the standard specifies that atoi is identical to
   // (int)(strtol).
-  auto result = internal::strtointeger<long>(str, 10);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  return static_cast<int>(result);
+  return static_cast<int>(
+      internal::str_to_helper<long, char>(str, nullptr, 10));
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/atol.cpp b/libc/src/stdlib/atol.cpp
index e1110ffa449b0..6b7d15fc6c955 100644
--- a/libc/src/stdlib/atol.cpp
+++ b/libc/src/stdlib/atol.cpp
@@ -7,19 +7,13 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/atol.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long, atol, (const char *str)) {
-  auto result = internal::strtointeger<long>(str, 10);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  return result;
+  return internal::str_to_helper<long, char>(str, nullptr, 10);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/atoll.cpp b/libc/src/stdlib/atoll.cpp
index 063e817f9b790..2945e2dd716d5 100644
--- a/libc/src/stdlib/atoll.cpp
+++ b/libc/src/stdlib/atoll.cpp
@@ -7,19 +7,13 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/atoll.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long long, atoll, (const char *str)) {
-  auto result = internal::strtointeger<long long>(str, 10);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  return result;
+  return internal::str_to_helper<long long, char>(str, nullptr, 10);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/str_to_util.h b/libc/src/stdlib/str_to_util.h
new file mode 100644
index 0000000000000..f6c3efd25ab3c
--- /dev/null
+++ b/libc/src/stdlib/str_to_util.h
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Shared implementation of strto* and wcsto* endpoints.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/str_to_integer.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace internal {
+
+// Shared implementation of strto* and wcsto* endpoints. Invokes
+// strtointeger shared API and sets errno and str_end pointer according to the
+// standard.
+template <typename T, typename CharType>
+LIBC_INLINE constexpr T str_to_helper(const CharType *__restrict str,
+                                      CharType **__restrict str_end, int base) {
+  auto result = strtointeger<T>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  // It is unspecified whether str_end should be set to "str" if the base
+  // is invalid, we explicitly avoid setting it for consistency with
+  // other implementations.
+  if (str_end != nullptr && result.error != EINVAL)
+    *str_end = const_cast<CharType *>(str + result.parsed_len);
+
+  return result.value;
+}
+
+} // namespace internal
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtol.cpp b/libc/src/stdlib/strtol.cpp
index 42db36b2052b4..e524fbd8c5213 100644
--- a/libc/src/stdlib/strtol.cpp
+++ b/libc/src/stdlib/strtol.cpp
@@ -7,24 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtol.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long, strtol,
                    (const char *__restrict str, char **__restrict str_end,
                     int base)) {
-  auto result = internal::strtointeger<long>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<char *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<long>(str, str_end, base);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtol_l.cpp b/libc/src/stdlib/strtol_l.cpp
index 497a4403eff4b..2fff7d220b830 100644
--- a/libc/src/stdlib/strtol_l.cpp
+++ b/libc/src/stdlib/strtol_l.cpp
@@ -7,24 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtol_l.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long, strtol_l,
                    (const char *__restrict str, char **__restrict str_end,
                     int base, locale_t)) {
-  auto result = internal::strtointeger<long>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<char *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<long>(str, str_end, base);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtoll.cpp b/libc/src/stdlib/strtoll.cpp
index c1dca13112e0f..a446755ecc4b1 100644
--- a/libc/src/stdlib/strtoll.cpp
+++ b/libc/src/stdlib/strtoll.cpp
@@ -7,24 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtoll.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long long, strtoll,
                    (const char *__restrict str, char **__restrict str_end,
                     int base)) {
-  auto result = internal::strtointeger<long long>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<char *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<long long>(str, str_end, base);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtoll_l.cpp b/libc/src/stdlib/strtoll_l.cpp
index 6f30d7794c5ca..1de15a6dc17a9 100644
--- a/libc/src/stdlib/strtoll_l.cpp
+++ b/libc/src/stdlib/strtoll_l.cpp
@@ -7,24 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtoll_l.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long long, strtoll_l,
                    (const char *__restrict str, char **__restrict str_end,
                     int base, locale_t)) {
-  auto result = internal::strtointeger<long long>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<char *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<long long>(str, str_end, base);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtoul.cpp b/libc/src/stdlib/strtoul.cpp
index d26ca5e5a10a1..d213c5e6edc52 100644
--- a/libc/src/stdlib/strtoul.cpp
+++ b/libc/src/stdlib/strtoul.cpp
@@ -7,24 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtoul.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(unsigned long, strtoul,
                    (const char *__restrict str, char **__restrict str_end,
                     int base)) {
-  auto result = internal::strtointeger<unsigned long>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<char *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<unsigned long>(str, str_end, base);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtoul_l.cpp b/libc/src/stdlib/strtoul_l.cpp
index 9a875ddee9029..38e8ff93a9174 100644
--- a/libc/src/stdlib/strtoul_l.cpp
+++ b/libc/src/stdlib/strtoul_l.cpp
@@ -7,24 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtoul_l.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(unsigned long, strtoul_l,
                    (const char *__restrict str, char **__restrict str_end,
                     int base, locale_t)) {
-  auto result = internal::strtointeger<unsigned long>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<char *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<unsigned long>(str, str_end, base);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtoull.cpp b/libc/src/stdlib/strtoull.cpp
index 8f929f577311e..87ca67f5396f3 100644
--- a/libc/src/stdlib/strtoull.cpp
+++ b/libc/src/stdlib/strtoull.cpp
@@ -7,24 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtoull.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(unsigned long long, strtoull,
                    (const char *__restrict str, char **__restrict str_end,
                     int base)) {
-  auto result = internal::strtointeger<unsigned long long>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<char *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<unsigned long long>(str, str_end, base);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtoull_l.cpp b/libc/src/stdlib/strtoull_l.cpp
index 9eb056b0e59b4..4651b66d90b2b 100644
--- a/libc/src/stdlib/strtoull_l.cpp
+++ b/libc/src/stdlib/strtoull_l.cpp
@@ -7,24 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtoull_l.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(unsigned long long, strtoull_l,
                    (const char *__restrict str, char **__restrict str_end,
                     int base, locale_t)) {
-  auto result = internal::strtointeger<unsigned long long>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<char *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<unsigned long long>(str, str_end, base);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/CMakeLists.txt b/libc/src/wchar/CMakeLists.txt
index eb340a80e5741..18c70703f1ef1 100644
--- a/libc/src/wchar/CMakeLists.txt
+++ b/libc/src/wchar/CMakeLists.txt
@@ -60,8 +60,7 @@ add_entrypoint_object(
   HDRS
     wcstol.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    libc.src.stdlib.str_to_util
 )
 
 add_entrypoint_object(
@@ -71,8 +70,7 @@ add_entrypoint_object(
   HDRS
     wcstoll.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    libc.src.stdlib.str_to_util
 )
 
 add_entrypoint_object(
@@ -82,8 +80,7 @@ add_entrypoint_object(
   HDRS
     wcstoul.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    libc.src.stdlib.str_to_util
 )
 
 add_entrypoint_object(
@@ -93,8 +90,7 @@ add_entrypoint_object(
   HDRS
     wcstoull.h
   DEPENDS
-    libc.src.errno.errno
-    libc.src.__support.str_to_integer
+    libc.src.stdlib.str_to_util
 )
 
 add_entrypoint_object(
diff --git a/libc/src/wchar/wcstol.cpp b/libc/src/wchar/wcstol.cpp
index a56b5f91272cd..d3131445a4816 100644
--- a/libc/src/wchar/wcstol.cpp
+++ b/libc/src/wchar/wcstol.cpp
@@ -7,24 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/wchar/wcstol.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long, wcstol,
                    (const wchar_t *__restrict str, wchar_t **__restrict str_end,
                     int base)) {
-  auto result = internal::strtointeger<long>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<wchar_t *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<long>(str, str_end, base);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcstoll.cpp b/libc/src/wchar/wcstoll.cpp
index 6229d24172b51..54c8520e4d5e7 100644
--- a/libc/src/wchar/wcstoll.cpp
+++ b/libc/src/wchar/wcstoll.cpp
@@ -7,24 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/wchar/wcstoll.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long long, wcstoll,
                    (const wchar_t *__restrict str, wchar_t **__restrict str_end,
                     int base)) {
-  auto result = internal::strtointeger<long long>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<wchar_t *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<long long>(str, str_end, base);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcstoul.cpp b/libc/src/wchar/wcstoul.cpp
index c5639bee1d649..28976b8ece350 100644
--- a/libc/src/wchar/wcstoul.cpp
+++ b/libc/src/wchar/wcstoul.cpp
@@ -7,24 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/wchar/wcstoul.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(unsigned long, wcstoul,
                    (const wchar_t *__restrict str, wchar_t **__restrict str_end,
                     int base)) {
-  auto result = internal::strtointeger<unsigned long>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<wchar_t *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<unsigned long>(str, str_end, base);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcstoull.cpp b/libc/src/wchar/wcstoull.cpp
index 2ab24e9b2b2a1..98599cfb6ee3f 100644
--- a/libc/src/wchar/wcstoull.cpp
+++ b/libc/src/wchar/wcstoull.cpp
@@ -7,24 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/wchar/wcstoull.h"
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(unsigned long long, wcstoull,
                    (const wchar_t *__restrict str, wchar_t **__restrict str_end,
                     int base)) {
-  auto result = internal::strtointeger<unsigned long long>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<wchar_t *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<unsigned long long>(str, str_end, base);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index a7941ead78bb9..9a3926655ff6b 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -100,8 +100,7 @@ add_libc_test(
     strtoint32_test.cpp
   DEPENDS
     libc.hdr.stdint_proxy
-    libc.src.__support.str_to_integer
-    libc.src.errno.errno
+    libc.src.stdlib.str_to_util
     .strtol_test_support
 )
 
@@ -113,8 +112,7 @@ add_libc_test(
     strtoint64_test.cpp
   DEPENDS
     libc.hdr.stdint_proxy
-    libc.src.__support.str_to_integer
-    libc.src.errno.errno
+    libc.src.stdlib.str_to_util
     .strtol_test_support
 )
 
diff --git a/libc/test/src/stdlib/StrtolTest.h b/libc/test/src/stdlib/StrtolTest.h
index 3a7da1fa85ac7..423e90c5b1ab6 100644
--- a/libc/test/src/stdlib/StrtolTest.h
+++ b/libc/test/src/stdlib/StrtolTest.h
@@ -28,8 +28,11 @@ struct StrtoTest : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {
 
   void InvalidBase(FunctionT func) {
     const char *ten = "10";
-    ASSERT_EQ(func(ten, nullptr, -1), ReturnT(0));
+    char *str_end = nullptr;
+    ASSERT_EQ(func(ten, &str_end, -1), ReturnT(0));
     ASSERT_ERRNO_EQ(EINVAL);
+    // Verify that str_end isn't touched for EINVAL errors.
+    ASSERT_EQ(str_end, nullptr);
   }
 
   void CleanBaseTenDecode(FunctionT func) {
diff --git a/libc/test/src/stdlib/strtoint32_test.cpp b/libc/test/src/stdlib/strtoint32_test.cpp
index 1bf199412e58c..d142fc4f76704 100644
--- a/libc/test/src/stdlib/strtoint32_test.cpp
+++ b/libc/test/src/stdlib/strtoint32_test.cpp
@@ -7,9 +7,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "hdr/stdint_proxy.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 #include "StrtolTest.h"
 #include "test/UnitTest/Test.h"
@@ -18,26 +17,12 @@ namespace LIBC_NAMESPACE_DECL {
 
 int32_t strtoint32(const char *__restrict str, char **__restrict str_end,
                    int base) {
-  auto result = internal::strtointeger<int32_t>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<char *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<int32_t>(str, str_end, base);
 }
 
 uint32_t strtouint32(const char *__restrict str, char **__restrict str_end,
                      int base) {
-  auto result = internal::strtointeger<uint32_t>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<char *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<uint32_t>(str, str_end, base);
 }
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/test/src/stdlib/strtoint64_test.cpp b/libc/test/src/stdlib/strtoint64_test.cpp
index 2b009d7cea690..0abbad041fa5d 100644
--- a/libc/test/src/stdlib/strtoint64_test.cpp
+++ b/libc/test/src/stdlib/strtoint64_test.cpp
@@ -7,9 +7,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "hdr/stdint_proxy.h"
-#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/str_to_integer.h"
+#include "src/stdlib/str_to_util.h"
 
 #include "StrtolTest.h"
 #include "test/UnitTest/Test.h"
@@ -18,26 +17,12 @@ namespace LIBC_NAMESPACE_DECL {
 
 int64_t strtoint64(const char *__restrict str, char **__restrict str_end,
                    int base) {
-  auto result = internal::strtointeger<int64_t>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<char *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<int64_t>(str, str_end, base);
 }
 
 uint64_t strtouint64(const char *__restrict str, char **__restrict str_end,
                      int base) {
-  auto result = internal::strtointeger<uint64_t>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  if (str_end != nullptr)
-    *str_end = const_cast<char *>(str + result.parsed_len);
-
-  return result;
+  return internal::str_to_helper<uint64_t>(str, str_end, base);
 }
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/test/src/wchar/WcstolTest.h b/libc/test/src/wchar/WcstolTest.h
index cadf9e0c42b90..ef1c5ec0ecafe 100644
--- a/libc/test/src/wchar/WcstolTest.h
+++ b/libc/test/src/wchar/WcstolTest.h
@@ -29,8 +29,11 @@ struct WcstoTest : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {
 
   void InvalidBase(FunctionT func) {
     const wchar_t *ten = L"10";
-    ASSERT_EQ(func(ten, nullptr, -1), ReturnT(0));
+    wchar_t *str_end = nullptr;
+    ASSERT_EQ(func(ten, &str_end, -1), ReturnT(0));
     ASSERT_ERRNO_EQ(EINVAL);
+    // Verify that str_end isn't touched for EINVAL errors.
+    ASSERT_EQ(str_end, nullptr);
   }
 
   void CleanBaseTenDecode(FunctionT func) {

>From f4992bbfde7610da259ad90cf04e1d8f22e5ac57 Mon Sep 17 00:00:00 2001
From: Alexey Samsonov <vonosmas at gmail.com>
Date: Wed, 27 May 2026 22:05:39 +0000
Subject: [PATCH 2/6] Fix bazel build

---
 .../llvm-project-overlay/libc/BUILD.bazel     | 47 ++++++++-----------
 1 file changed, 20 insertions(+), 27 deletions(-)

diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
index 186f77b030f3a..479aa3c917d8a 100644
--- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
@@ -3105,6 +3105,7 @@ cc_library(
         ":__support_macros_properties_compiler",
         ":__support_macros_properties_cpu_features",
         ":__support_macros_properties_types",
+        ":__support_math_extras",
         ":__support_number_pair",
         ":__support_range_reduction",
         ":__support_range_reduction_double",
@@ -12915,11 +12916,9 @@ libc_function(
     hdrs = ["src/inttypes/strtoimax.h"],
     deps = [
         ":__support_common",
-        ":__support_libc_errno",
         ":__support_macros_config",
-        ":__support_str_to_integer",
-        ":errno",
         ":hdr_stdint_proxy",
+        ":str_to_util",
     ],
 )
 
@@ -12929,11 +12928,9 @@ libc_function(
     hdrs = ["src/inttypes/strtoumax.h"],
     deps = [
         ":__support_common",
-        ":__support_libc_errno",
         ":__support_macros_config",
-        ":__support_str_to_integer",
-        ":errno",
         ":hdr_stdint_proxy",
+        ":str_to_util",
     ],
 )
 
@@ -13096,10 +13093,8 @@ libc_function(
     hdrs = ["src/stdlib/atoi.h"],
     deps = [
         ":__support_common",
-        ":__support_libc_errno",
         ":__support_macros_config",
-        ":__support_str_to_integer",
-        ":errno",
+        ":str_to_util",
     ],
 )
 
@@ -13109,10 +13104,8 @@ libc_function(
     hdrs = ["src/stdlib/atol.h"],
     deps = [
         ":__support_common",
-        ":__support_libc_errno",
         ":__support_macros_config",
-        ":__support_str_to_integer",
-        ":errno",
+        ":str_to_util",
     ],
 )
 
@@ -13122,10 +13115,8 @@ libc_function(
     hdrs = ["src/stdlib/atoll.h"],
     deps = [
         ":__support_common",
-        ":__support_libc_errno",
         ":__support_macros_config",
-        ":__support_str_to_integer",
-        ":errno",
+        ":str_to_util",
     ],
 )
 
@@ -13234,6 +13225,16 @@ libc_function(
     ],
 )
 
+libc_support_library(
+    name = "str_to_util",
+    hdrs = ["src/stdlib/str_to_util.h"],
+    deps = [
+        ":__support_common",
+        ":__support_libc_errno",
+        ":__support_str_to_integer",
+    ],
+)
+
 libc_support_library(
     name = "str_from_util",
     hdrs = ["src/stdlib/str_from_util.h"],
@@ -13296,10 +13297,8 @@ libc_function(
     hdrs = ["src/stdlib/strtol.h"],
     deps = [
         ":__support_common",
-        ":__support_libc_errno",
         ":__support_macros_config",
-        ":__support_str_to_integer",
-        ":errno",
+        ":str_to_util",
     ],
 )
 
@@ -13309,10 +13308,8 @@ libc_function(
     hdrs = ["src/stdlib/strtoll.h"],
     deps = [
         ":__support_common",
-        ":__support_libc_errno",
         ":__support_macros_config",
-        ":__support_str_to_integer",
-        ":errno",
+        ":str_to_util",
     ],
 )
 
@@ -13322,10 +13319,8 @@ libc_function(
     hdrs = ["src/stdlib/strtoul.h"],
     deps = [
         ":__support_common",
-        ":__support_libc_errno",
         ":__support_macros_config",
-        ":__support_str_to_integer",
-        ":errno",
+        ":str_to_util",
     ],
 )
 
@@ -13335,10 +13330,8 @@ libc_function(
     hdrs = ["src/stdlib/strtoull.h"],
     deps = [
         ":__support_common",
-        ":__support_libc_errno",
         ":__support_macros_config",
-        ":__support_str_to_integer",
-        ":errno",
+        ":str_to_util",
     ],
 )
 

>From 3e03668d8a8d0ba3598a66fa3cd2d7cce30b55b1 Mon Sep 17 00:00:00 2001
From: Alexey Samsonov <vonosmas at gmail.com>
Date: Wed, 27 May 2026 23:44:28 +0000
Subject: [PATCH 3/6] Update CMake dependency

---
 libc/src/stdlib/CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 2299bc1f0b0c5..e84b90988c6fa 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -147,8 +147,8 @@ add_header_library(
     str_to_util.h
   DEPENDS
     libc.src.__support.common
-    libc.src.__support.libc_errno
     libc.src.__support.str_to_integer
+    libc.src.errno.errno
 )
 
 add_entrypoint_object(

>From 338f344fe97157baec241da4462769cb878992e5 Mon Sep 17 00:00:00 2001
From: Alexey Samsonov <vonosmas at gmail.com>
Date: Thu, 28 May 2026 04:51:22 +0000
Subject: [PATCH 4/6] Revert past 3 commits except for StrtolTest.h and
 WcstolTest.h

---
 libc/src/inttypes/CMakeLists.txt              |  6 ++-
 libc/src/inttypes/strtoimax.cpp               | 13 ++++-
 libc/src/inttypes/strtoumax.cpp               | 13 ++++-
 libc/src/stdlib/CMakeLists.txt                | 43 ++++++++---------
 libc/src/stdlib/atoi.cpp                      | 11 +++--
 libc/src/stdlib/atol.cpp                      | 10 +++-
 libc/src/stdlib/atoll.cpp                     | 10 +++-
 libc/src/stdlib/str_to_util.h                 | 41 ----------------
 libc/src/stdlib/strtol.cpp                    | 13 ++++-
 libc/src/stdlib/strtol_l.cpp                  | 13 ++++-
 libc/src/stdlib/strtoll.cpp                   | 13 ++++-
 libc/src/stdlib/strtoll_l.cpp                 | 13 ++++-
 libc/src/stdlib/strtoul.cpp                   | 13 ++++-
 libc/src/stdlib/strtoul_l.cpp                 | 13 ++++-
 libc/src/stdlib/strtoull.cpp                  | 13 ++++-
 libc/src/stdlib/strtoull_l.cpp                | 13 ++++-
 libc/src/wchar/CMakeLists.txt                 | 12 +++--
 libc/src/wchar/wcstol.cpp                     | 13 ++++-
 libc/src/wchar/wcstoll.cpp                    | 13 ++++-
 libc/src/wchar/wcstoul.cpp                    | 13 ++++-
 libc/src/wchar/wcstoull.cpp                   | 13 ++++-
 libc/test/src/stdlib/CMakeLists.txt           |  6 ++-
 libc/test/src/stdlib/strtoint32_test.cpp      | 21 +++++++--
 libc/test/src/stdlib/strtoint64_test.cpp      | 21 +++++++--
 .../llvm-project-overlay/libc/BUILD.bazel     | 47 +++++++++++--------
 25 files changed, 279 insertions(+), 131 deletions(-)
 delete mode 100644 libc/src/stdlib/str_to_util.h

diff --git a/libc/src/inttypes/CMakeLists.txt b/libc/src/inttypes/CMakeLists.txt
index eb9f2f3375eab..3a48c9a1a8b97 100644
--- a/libc/src/inttypes/CMakeLists.txt
+++ b/libc/src/inttypes/CMakeLists.txt
@@ -6,7 +6,8 @@ add_entrypoint_object(
     strtoimax.h
   DEPENDS
     libc.hdr.stdint_proxy
-    libc.src.stdlib.str_to_util
+    libc.src.__support.str_to_integer
+    libc.src.errno.errno
 )
 
 add_entrypoint_object(
@@ -17,7 +18,8 @@ add_entrypoint_object(
     strtoumax.h
   DEPENDS
     libc.hdr.stdint_proxy
-    libc.src.stdlib.str_to_util
+    libc.src.__support.str_to_integer
+    libc.src.errno.errno
 )
 
 add_entrypoint_object(
diff --git a/libc/src/inttypes/strtoimax.cpp b/libc/src/inttypes/strtoimax.cpp
index 213d8800aab80..6e55a4b56aac7 100644
--- a/libc/src/inttypes/strtoimax.cpp
+++ b/libc/src/inttypes/strtoimax.cpp
@@ -7,15 +7,24 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/inttypes/strtoimax.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(intmax_t, strtoimax,
                    (const char *__restrict str, char **__restrict str_end,
                     int base)) {
-  return internal::str_to_helper<intmax_t>(str, str_end, base);
+  auto result = internal::strtointeger<intmax_t>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<char *>(str + result.parsed_len);
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/inttypes/strtoumax.cpp b/libc/src/inttypes/strtoumax.cpp
index 6191ad0491bf2..ce5a0a782d979 100644
--- a/libc/src/inttypes/strtoumax.cpp
+++ b/libc/src/inttypes/strtoumax.cpp
@@ -7,15 +7,24 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/inttypes/strtoumax.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(uintmax_t, strtoumax,
                    (const char *__restrict str, char **__restrict str_end,
                     int base)) {
-  return internal::str_to_helper<uintmax_t>(str, str_end, base);
+  auto result = internal::strtointeger<uintmax_t>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<char *>(str + result.parsed_len);
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index e84b90988c6fa..7e542e56a983d 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -5,7 +5,8 @@ add_entrypoint_object(
   HDRS
     atoi.h
   DEPENDS
-    .str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 add_entrypoint_object(
@@ -26,7 +27,8 @@ add_entrypoint_object(
   HDRS
     atol.h
   DEPENDS
-    .str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 add_entrypoint_object(
@@ -36,7 +38,8 @@ add_entrypoint_object(
   HDRS
     atoll.h
   DEPENDS
-    .str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 add_entrypoint_object(
@@ -141,16 +144,6 @@ add_header_library(
     libc.src.__support.CPP.type_traits
 )
 
-add_header_library(
-  str_to_util
-  HDRS
-    str_to_util.h
-  DEPENDS
-    libc.src.__support.common
-    libc.src.__support.str_to_integer
-    libc.src.errno.errno
-)
-
 add_entrypoint_object(
   strtof
   SRCS
@@ -191,7 +184,8 @@ add_entrypoint_object(
   HDRS
     strtol.h
   DEPENDS
-    .str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 add_entrypoint_object(
@@ -201,7 +195,8 @@ add_entrypoint_object(
   HDRS
     strtoll.h
   DEPENDS
-    .str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 add_entrypoint_object(
@@ -211,7 +206,8 @@ add_entrypoint_object(
   HDRS
     strtoul.h
   DEPENDS
-    .str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 add_entrypoint_object(
@@ -221,7 +217,8 @@ add_entrypoint_object(
   HDRS
     strtoull.h
   DEPENDS
-    .str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 add_entrypoint_object(
@@ -615,7 +612,8 @@ add_entrypoint_object(
   HDRS
     strtol_l.h
   DEPENDS
-    .str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 add_entrypoint_object(
@@ -625,7 +623,8 @@ add_entrypoint_object(
   HDRS
     strtoll_l.h
   DEPENDS
-    .str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 add_entrypoint_object(
@@ -635,7 +634,8 @@ add_entrypoint_object(
   HDRS
     strtoul_l.h
   DEPENDS
-    .str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 add_entrypoint_object(
@@ -645,7 +645,8 @@ add_entrypoint_object(
   HDRS
     strtoull_l.h
   DEPENDS
-    .str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
diff --git a/libc/src/stdlib/atoi.cpp b/libc/src/stdlib/atoi.cpp
index 9b030685a603b..420bbc8143d55 100644
--- a/libc/src/stdlib/atoi.cpp
+++ b/libc/src/stdlib/atoi.cpp
@@ -7,16 +7,21 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/atoi.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(int, atoi, (const char *str)) {
   // This is done because the standard specifies that atoi is identical to
   // (int)(strtol).
-  return static_cast<int>(
-      internal::str_to_helper<long, char>(str, nullptr, 10));
+  auto result = internal::strtointeger<long>(str, 10);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  return static_cast<int>(result);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/atol.cpp b/libc/src/stdlib/atol.cpp
index 6b7d15fc6c955..e1110ffa449b0 100644
--- a/libc/src/stdlib/atol.cpp
+++ b/libc/src/stdlib/atol.cpp
@@ -7,13 +7,19 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/atol.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long, atol, (const char *str)) {
-  return internal::str_to_helper<long, char>(str, nullptr, 10);
+  auto result = internal::strtointeger<long>(str, 10);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/atoll.cpp b/libc/src/stdlib/atoll.cpp
index 2945e2dd716d5..063e817f9b790 100644
--- a/libc/src/stdlib/atoll.cpp
+++ b/libc/src/stdlib/atoll.cpp
@@ -7,13 +7,19 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/atoll.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long long, atoll, (const char *str)) {
-  return internal::str_to_helper<long long, char>(str, nullptr, 10);
+  auto result = internal::strtointeger<long long>(str, 10);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/str_to_util.h b/libc/src/stdlib/str_to_util.h
deleted file mode 100644
index f6c3efd25ab3c..0000000000000
--- a/libc/src/stdlib/str_to_util.h
+++ /dev/null
@@ -1,41 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-///
-/// \file
-/// Shared implementation of strto* and wcsto* endpoints.
-///
-//===----------------------------------------------------------------------===//
-
-#include "src/__support/common.h"
-#include "src/__support/libc_errno.h"
-#include "src/__support/str_to_integer.h"
-
-namespace LIBC_NAMESPACE_DECL {
-namespace internal {
-
-// Shared implementation of strto* and wcsto* endpoints. Invokes
-// strtointeger shared API and sets errno and str_end pointer according to the
-// standard.
-template <typename T, typename CharType>
-LIBC_INLINE constexpr T str_to_helper(const CharType *__restrict str,
-                                      CharType **__restrict str_end, int base) {
-  auto result = strtointeger<T>(str, base);
-  if (result.has_error())
-    libc_errno = result.error;
-
-  // It is unspecified whether str_end should be set to "str" if the base
-  // is invalid, we explicitly avoid setting it for consistency with
-  // other implementations.
-  if (str_end != nullptr && result.error != EINVAL)
-    *str_end = const_cast<CharType *>(str + result.parsed_len);
-
-  return result.value;
-}
-
-} // namespace internal
-} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtol.cpp b/libc/src/stdlib/strtol.cpp
index e524fbd8c5213..42db36b2052b4 100644
--- a/libc/src/stdlib/strtol.cpp
+++ b/libc/src/stdlib/strtol.cpp
@@ -7,15 +7,24 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtol.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long, strtol,
                    (const char *__restrict str, char **__restrict str_end,
                     int base)) {
-  return internal::str_to_helper<long>(str, str_end, base);
+  auto result = internal::strtointeger<long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<char *>(str + result.parsed_len);
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtol_l.cpp b/libc/src/stdlib/strtol_l.cpp
index 2fff7d220b830..497a4403eff4b 100644
--- a/libc/src/stdlib/strtol_l.cpp
+++ b/libc/src/stdlib/strtol_l.cpp
@@ -7,15 +7,24 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtol_l.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long, strtol_l,
                    (const char *__restrict str, char **__restrict str_end,
                     int base, locale_t)) {
-  return internal::str_to_helper<long>(str, str_end, base);
+  auto result = internal::strtointeger<long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<char *>(str + result.parsed_len);
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtoll.cpp b/libc/src/stdlib/strtoll.cpp
index a446755ecc4b1..c1dca13112e0f 100644
--- a/libc/src/stdlib/strtoll.cpp
+++ b/libc/src/stdlib/strtoll.cpp
@@ -7,15 +7,24 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtoll.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long long, strtoll,
                    (const char *__restrict str, char **__restrict str_end,
                     int base)) {
-  return internal::str_to_helper<long long>(str, str_end, base);
+  auto result = internal::strtointeger<long long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<char *>(str + result.parsed_len);
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtoll_l.cpp b/libc/src/stdlib/strtoll_l.cpp
index 1de15a6dc17a9..6f30d7794c5ca 100644
--- a/libc/src/stdlib/strtoll_l.cpp
+++ b/libc/src/stdlib/strtoll_l.cpp
@@ -7,15 +7,24 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtoll_l.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long long, strtoll_l,
                    (const char *__restrict str, char **__restrict str_end,
                     int base, locale_t)) {
-  return internal::str_to_helper<long long>(str, str_end, base);
+  auto result = internal::strtointeger<long long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<char *>(str + result.parsed_len);
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtoul.cpp b/libc/src/stdlib/strtoul.cpp
index d213c5e6edc52..d26ca5e5a10a1 100644
--- a/libc/src/stdlib/strtoul.cpp
+++ b/libc/src/stdlib/strtoul.cpp
@@ -7,15 +7,24 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtoul.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(unsigned long, strtoul,
                    (const char *__restrict str, char **__restrict str_end,
                     int base)) {
-  return internal::str_to_helper<unsigned long>(str, str_end, base);
+  auto result = internal::strtointeger<unsigned long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<char *>(str + result.parsed_len);
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtoul_l.cpp b/libc/src/stdlib/strtoul_l.cpp
index 38e8ff93a9174..9a875ddee9029 100644
--- a/libc/src/stdlib/strtoul_l.cpp
+++ b/libc/src/stdlib/strtoul_l.cpp
@@ -7,15 +7,24 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtoul_l.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(unsigned long, strtoul_l,
                    (const char *__restrict str, char **__restrict str_end,
                     int base, locale_t)) {
-  return internal::str_to_helper<unsigned long>(str, str_end, base);
+  auto result = internal::strtointeger<unsigned long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<char *>(str + result.parsed_len);
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtoull.cpp b/libc/src/stdlib/strtoull.cpp
index 87ca67f5396f3..8f929f577311e 100644
--- a/libc/src/stdlib/strtoull.cpp
+++ b/libc/src/stdlib/strtoull.cpp
@@ -7,15 +7,24 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtoull.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(unsigned long long, strtoull,
                    (const char *__restrict str, char **__restrict str_end,
                     int base)) {
-  return internal::str_to_helper<unsigned long long>(str, str_end, base);
+  auto result = internal::strtointeger<unsigned long long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<char *>(str + result.parsed_len);
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/strtoull_l.cpp b/libc/src/stdlib/strtoull_l.cpp
index 4651b66d90b2b..9eb056b0e59b4 100644
--- a/libc/src/stdlib/strtoull_l.cpp
+++ b/libc/src/stdlib/strtoull_l.cpp
@@ -7,15 +7,24 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdlib/strtoull_l.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(unsigned long long, strtoull_l,
                    (const char *__restrict str, char **__restrict str_end,
                     int base, locale_t)) {
-  return internal::str_to_helper<unsigned long long>(str, str_end, base);
+  auto result = internal::strtointeger<unsigned long long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<char *>(str + result.parsed_len);
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/CMakeLists.txt b/libc/src/wchar/CMakeLists.txt
index 18c70703f1ef1..eb340a80e5741 100644
--- a/libc/src/wchar/CMakeLists.txt
+++ b/libc/src/wchar/CMakeLists.txt
@@ -60,7 +60,8 @@ add_entrypoint_object(
   HDRS
     wcstol.h
   DEPENDS
-    libc.src.stdlib.str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 add_entrypoint_object(
@@ -70,7 +71,8 @@ add_entrypoint_object(
   HDRS
     wcstoll.h
   DEPENDS
-    libc.src.stdlib.str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 add_entrypoint_object(
@@ -80,7 +82,8 @@ add_entrypoint_object(
   HDRS
     wcstoul.h
   DEPENDS
-    libc.src.stdlib.str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 add_entrypoint_object(
@@ -90,7 +93,8 @@ add_entrypoint_object(
   HDRS
     wcstoull.h
   DEPENDS
-    libc.src.stdlib.str_to_util
+    libc.src.errno.errno
+    libc.src.__support.str_to_integer
 )
 
 add_entrypoint_object(
diff --git a/libc/src/wchar/wcstol.cpp b/libc/src/wchar/wcstol.cpp
index d3131445a4816..a56b5f91272cd 100644
--- a/libc/src/wchar/wcstol.cpp
+++ b/libc/src/wchar/wcstol.cpp
@@ -7,15 +7,24 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/wchar/wcstol.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long, wcstol,
                    (const wchar_t *__restrict str, wchar_t **__restrict str_end,
                     int base)) {
-  return internal::str_to_helper<long>(str, str_end, base);
+  auto result = internal::strtointeger<long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<wchar_t *>(str + result.parsed_len);
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcstoll.cpp b/libc/src/wchar/wcstoll.cpp
index 54c8520e4d5e7..6229d24172b51 100644
--- a/libc/src/wchar/wcstoll.cpp
+++ b/libc/src/wchar/wcstoll.cpp
@@ -7,15 +7,24 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/wchar/wcstoll.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long long, wcstoll,
                    (const wchar_t *__restrict str, wchar_t **__restrict str_end,
                     int base)) {
-  return internal::str_to_helper<long long>(str, str_end, base);
+  auto result = internal::strtointeger<long long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<wchar_t *>(str + result.parsed_len);
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcstoul.cpp b/libc/src/wchar/wcstoul.cpp
index 28976b8ece350..c5639bee1d649 100644
--- a/libc/src/wchar/wcstoul.cpp
+++ b/libc/src/wchar/wcstoul.cpp
@@ -7,15 +7,24 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/wchar/wcstoul.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(unsigned long, wcstoul,
                    (const wchar_t *__restrict str, wchar_t **__restrict str_end,
                     int base)) {
-  return internal::str_to_helper<unsigned long>(str, str_end, base);
+  auto result = internal::strtointeger<unsigned long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<wchar_t *>(str + result.parsed_len);
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/wchar/wcstoull.cpp b/libc/src/wchar/wcstoull.cpp
index 98599cfb6ee3f..2ab24e9b2b2a1 100644
--- a/libc/src/wchar/wcstoull.cpp
+++ b/libc/src/wchar/wcstoull.cpp
@@ -7,15 +7,24 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/wchar/wcstoull.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(unsigned long long, wcstoull,
                    (const wchar_t *__restrict str, wchar_t **__restrict str_end,
                     int base)) {
-  return internal::str_to_helper<unsigned long long>(str, str_end, base);
+  auto result = internal::strtointeger<unsigned long long>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<wchar_t *>(str + result.parsed_len);
+
+  return result;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index 9a3926655ff6b..a7941ead78bb9 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -100,7 +100,8 @@ add_libc_test(
     strtoint32_test.cpp
   DEPENDS
     libc.hdr.stdint_proxy
-    libc.src.stdlib.str_to_util
+    libc.src.__support.str_to_integer
+    libc.src.errno.errno
     .strtol_test_support
 )
 
@@ -112,7 +113,8 @@ add_libc_test(
     strtoint64_test.cpp
   DEPENDS
     libc.hdr.stdint_proxy
-    libc.src.stdlib.str_to_util
+    libc.src.__support.str_to_integer
+    libc.src.errno.errno
     .strtol_test_support
 )
 
diff --git a/libc/test/src/stdlib/strtoint32_test.cpp b/libc/test/src/stdlib/strtoint32_test.cpp
index d142fc4f76704..1bf199412e58c 100644
--- a/libc/test/src/stdlib/strtoint32_test.cpp
+++ b/libc/test/src/stdlib/strtoint32_test.cpp
@@ -7,8 +7,9 @@
 //===----------------------------------------------------------------------===//
 
 #include "hdr/stdint_proxy.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 #include "StrtolTest.h"
 #include "test/UnitTest/Test.h"
@@ -17,12 +18,26 @@ namespace LIBC_NAMESPACE_DECL {
 
 int32_t strtoint32(const char *__restrict str, char **__restrict str_end,
                    int base) {
-  return internal::str_to_helper<int32_t>(str, str_end, base);
+  auto result = internal::strtointeger<int32_t>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<char *>(str + result.parsed_len);
+
+  return result;
 }
 
 uint32_t strtouint32(const char *__restrict str, char **__restrict str_end,
                      int base) {
-  return internal::str_to_helper<uint32_t>(str, str_end, base);
+  auto result = internal::strtointeger<uint32_t>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<char *>(str + result.parsed_len);
+
+  return result;
 }
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/test/src/stdlib/strtoint64_test.cpp b/libc/test/src/stdlib/strtoint64_test.cpp
index 0abbad041fa5d..2b009d7cea690 100644
--- a/libc/test/src/stdlib/strtoint64_test.cpp
+++ b/libc/test/src/stdlib/strtoint64_test.cpp
@@ -7,8 +7,9 @@
 //===----------------------------------------------------------------------===//
 
 #include "hdr/stdint_proxy.h"
+#include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/str_to_util.h"
+#include "src/__support/str_to_integer.h"
 
 #include "StrtolTest.h"
 #include "test/UnitTest/Test.h"
@@ -17,12 +18,26 @@ namespace LIBC_NAMESPACE_DECL {
 
 int64_t strtoint64(const char *__restrict str, char **__restrict str_end,
                    int base) {
-  return internal::str_to_helper<int64_t>(str, str_end, base);
+  auto result = internal::strtointeger<int64_t>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<char *>(str + result.parsed_len);
+
+  return result;
 }
 
 uint64_t strtouint64(const char *__restrict str, char **__restrict str_end,
                      int base) {
-  return internal::str_to_helper<uint64_t>(str, str_end, base);
+  auto result = internal::strtointeger<uint64_t>(str, base);
+  if (result.has_error())
+    libc_errno = result.error;
+
+  if (str_end != nullptr)
+    *str_end = const_cast<char *>(str + result.parsed_len);
+
+  return result;
 }
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
index 479aa3c917d8a..186f77b030f3a 100644
--- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
@@ -3105,7 +3105,6 @@ cc_library(
         ":__support_macros_properties_compiler",
         ":__support_macros_properties_cpu_features",
         ":__support_macros_properties_types",
-        ":__support_math_extras",
         ":__support_number_pair",
         ":__support_range_reduction",
         ":__support_range_reduction_double",
@@ -12916,9 +12915,11 @@ libc_function(
     hdrs = ["src/inttypes/strtoimax.h"],
     deps = [
         ":__support_common",
+        ":__support_libc_errno",
         ":__support_macros_config",
+        ":__support_str_to_integer",
+        ":errno",
         ":hdr_stdint_proxy",
-        ":str_to_util",
     ],
 )
 
@@ -12928,9 +12929,11 @@ libc_function(
     hdrs = ["src/inttypes/strtoumax.h"],
     deps = [
         ":__support_common",
+        ":__support_libc_errno",
         ":__support_macros_config",
+        ":__support_str_to_integer",
+        ":errno",
         ":hdr_stdint_proxy",
-        ":str_to_util",
     ],
 )
 
@@ -13093,8 +13096,10 @@ libc_function(
     hdrs = ["src/stdlib/atoi.h"],
     deps = [
         ":__support_common",
+        ":__support_libc_errno",
         ":__support_macros_config",
-        ":str_to_util",
+        ":__support_str_to_integer",
+        ":errno",
     ],
 )
 
@@ -13104,8 +13109,10 @@ libc_function(
     hdrs = ["src/stdlib/atol.h"],
     deps = [
         ":__support_common",
+        ":__support_libc_errno",
         ":__support_macros_config",
-        ":str_to_util",
+        ":__support_str_to_integer",
+        ":errno",
     ],
 )
 
@@ -13115,8 +13122,10 @@ libc_function(
     hdrs = ["src/stdlib/atoll.h"],
     deps = [
         ":__support_common",
+        ":__support_libc_errno",
         ":__support_macros_config",
-        ":str_to_util",
+        ":__support_str_to_integer",
+        ":errno",
     ],
 )
 
@@ -13225,16 +13234,6 @@ libc_function(
     ],
 )
 
-libc_support_library(
-    name = "str_to_util",
-    hdrs = ["src/stdlib/str_to_util.h"],
-    deps = [
-        ":__support_common",
-        ":__support_libc_errno",
-        ":__support_str_to_integer",
-    ],
-)
-
 libc_support_library(
     name = "str_from_util",
     hdrs = ["src/stdlib/str_from_util.h"],
@@ -13297,8 +13296,10 @@ libc_function(
     hdrs = ["src/stdlib/strtol.h"],
     deps = [
         ":__support_common",
+        ":__support_libc_errno",
         ":__support_macros_config",
-        ":str_to_util",
+        ":__support_str_to_integer",
+        ":errno",
     ],
 )
 
@@ -13308,8 +13309,10 @@ libc_function(
     hdrs = ["src/stdlib/strtoll.h"],
     deps = [
         ":__support_common",
+        ":__support_libc_errno",
         ":__support_macros_config",
-        ":str_to_util",
+        ":__support_str_to_integer",
+        ":errno",
     ],
 )
 
@@ -13319,8 +13322,10 @@ libc_function(
     hdrs = ["src/stdlib/strtoul.h"],
     deps = [
         ":__support_common",
+        ":__support_libc_errno",
         ":__support_macros_config",
-        ":str_to_util",
+        ":__support_str_to_integer",
+        ":errno",
     ],
 )
 
@@ -13330,8 +13335,10 @@ libc_function(
     hdrs = ["src/stdlib/strtoull.h"],
     deps = [
         ":__support_common",
+        ":__support_libc_errno",
         ":__support_macros_config",
-        ":str_to_util",
+        ":__support_str_to_integer",
+        ":errno",
     ],
 )
 

>From 9e64f26ae9af447deca043be70e33adaac9519c2 Mon Sep 17 00:00:00 2001
From: Alexey Samsonov <vonosmas at gmail.com>
Date: Thu, 28 May 2026 04:57:52 +0000
Subject: [PATCH 5/6] Explicitly add EINVAL checks in all strto/wcsto
 functions.

---
 libc/src/inttypes/strtoimax.cpp          | 2 +-
 libc/src/inttypes/strtoumax.cpp          | 2 +-
 libc/src/stdlib/strtol.cpp               | 2 +-
 libc/src/stdlib/strtol_l.cpp             | 2 +-
 libc/src/stdlib/strtoll.cpp              | 2 +-
 libc/src/stdlib/strtoll_l.cpp            | 2 +-
 libc/src/stdlib/strtoul.cpp              | 2 +-
 libc/src/stdlib/strtoul_l.cpp            | 2 +-
 libc/src/stdlib/strtoull.cpp             | 2 +-
 libc/src/wchar/wcstol.cpp                | 2 +-
 libc/src/wchar/wcstoll.cpp               | 2 +-
 libc/src/wchar/wcstoul.cpp               | 2 +-
 libc/src/wchar/wcstoull.cpp              | 2 +-
 libc/test/src/stdlib/strtoint32_test.cpp | 4 ++--
 libc/test/src/stdlib/strtoint64_test.cpp | 4 ++--
 15 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/libc/src/inttypes/strtoimax.cpp b/libc/src/inttypes/strtoimax.cpp
index 6e55a4b56aac7..4e9265534b6d3 100644
--- a/libc/src/inttypes/strtoimax.cpp
+++ b/libc/src/inttypes/strtoimax.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(intmax_t, strtoimax,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<char *>(str + result.parsed_len);
 
   return result;
diff --git a/libc/src/inttypes/strtoumax.cpp b/libc/src/inttypes/strtoumax.cpp
index ce5a0a782d979..93888d936d828 100644
--- a/libc/src/inttypes/strtoumax.cpp
+++ b/libc/src/inttypes/strtoumax.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(uintmax_t, strtoumax,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<char *>(str + result.parsed_len);
 
   return result;
diff --git a/libc/src/stdlib/strtol.cpp b/libc/src/stdlib/strtol.cpp
index 42db36b2052b4..54c680acaf3d5 100644
--- a/libc/src/stdlib/strtol.cpp
+++ b/libc/src/stdlib/strtol.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(long, strtol,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<char *>(str + result.parsed_len);
 
   return result;
diff --git a/libc/src/stdlib/strtol_l.cpp b/libc/src/stdlib/strtol_l.cpp
index 497a4403eff4b..8ec5fb76fdf47 100644
--- a/libc/src/stdlib/strtol_l.cpp
+++ b/libc/src/stdlib/strtol_l.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(long, strtol_l,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<char *>(str + result.parsed_len);
 
   return result;
diff --git a/libc/src/stdlib/strtoll.cpp b/libc/src/stdlib/strtoll.cpp
index c1dca13112e0f..5557c6f393cd3 100644
--- a/libc/src/stdlib/strtoll.cpp
+++ b/libc/src/stdlib/strtoll.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(long long, strtoll,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<char *>(str + result.parsed_len);
 
   return result;
diff --git a/libc/src/stdlib/strtoll_l.cpp b/libc/src/stdlib/strtoll_l.cpp
index 6f30d7794c5ca..32bdf4babb748 100644
--- a/libc/src/stdlib/strtoll_l.cpp
+++ b/libc/src/stdlib/strtoll_l.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(long long, strtoll_l,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<char *>(str + result.parsed_len);
 
   return result;
diff --git a/libc/src/stdlib/strtoul.cpp b/libc/src/stdlib/strtoul.cpp
index d26ca5e5a10a1..1184c13c175e5 100644
--- a/libc/src/stdlib/strtoul.cpp
+++ b/libc/src/stdlib/strtoul.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(unsigned long, strtoul,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<char *>(str + result.parsed_len);
 
   return result;
diff --git a/libc/src/stdlib/strtoul_l.cpp b/libc/src/stdlib/strtoul_l.cpp
index 9a875ddee9029..d90d2e856f24d 100644
--- a/libc/src/stdlib/strtoul_l.cpp
+++ b/libc/src/stdlib/strtoul_l.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(unsigned long, strtoul_l,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<char *>(str + result.parsed_len);
 
   return result;
diff --git a/libc/src/stdlib/strtoull.cpp b/libc/src/stdlib/strtoull.cpp
index 8f929f577311e..571821097cb47 100644
--- a/libc/src/stdlib/strtoull.cpp
+++ b/libc/src/stdlib/strtoull.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(unsigned long long, strtoull,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<char *>(str + result.parsed_len);
 
   return result;
diff --git a/libc/src/wchar/wcstol.cpp b/libc/src/wchar/wcstol.cpp
index a56b5f91272cd..f381f558128d2 100644
--- a/libc/src/wchar/wcstol.cpp
+++ b/libc/src/wchar/wcstol.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(long, wcstol,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<wchar_t *>(str + result.parsed_len);
 
   return result;
diff --git a/libc/src/wchar/wcstoll.cpp b/libc/src/wchar/wcstoll.cpp
index 6229d24172b51..5d89eb679b3db 100644
--- a/libc/src/wchar/wcstoll.cpp
+++ b/libc/src/wchar/wcstoll.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(long long, wcstoll,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<wchar_t *>(str + result.parsed_len);
 
   return result;
diff --git a/libc/src/wchar/wcstoul.cpp b/libc/src/wchar/wcstoul.cpp
index c5639bee1d649..ee7516652dbb9 100644
--- a/libc/src/wchar/wcstoul.cpp
+++ b/libc/src/wchar/wcstoul.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(unsigned long, wcstoul,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<wchar_t *>(str + result.parsed_len);
 
   return result;
diff --git a/libc/src/wchar/wcstoull.cpp b/libc/src/wchar/wcstoull.cpp
index 2ab24e9b2b2a1..301cb66f003b8 100644
--- a/libc/src/wchar/wcstoull.cpp
+++ b/libc/src/wchar/wcstoull.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(unsigned long long, wcstoull,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<wchar_t *>(str + result.parsed_len);
 
   return result;
diff --git a/libc/test/src/stdlib/strtoint32_test.cpp b/libc/test/src/stdlib/strtoint32_test.cpp
index 1bf199412e58c..77a3cb30ba06c 100644
--- a/libc/test/src/stdlib/strtoint32_test.cpp
+++ b/libc/test/src/stdlib/strtoint32_test.cpp
@@ -22,7 +22,7 @@ int32_t strtoint32(const char *__restrict str, char **__restrict str_end,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<char *>(str + result.parsed_len);
 
   return result;
@@ -34,7 +34,7 @@ uint32_t strtouint32(const char *__restrict str, char **__restrict str_end,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<char *>(str + result.parsed_len);
 
   return result;
diff --git a/libc/test/src/stdlib/strtoint64_test.cpp b/libc/test/src/stdlib/strtoint64_test.cpp
index 2b009d7cea690..b8f5bfd74f0c4 100644
--- a/libc/test/src/stdlib/strtoint64_test.cpp
+++ b/libc/test/src/stdlib/strtoint64_test.cpp
@@ -22,7 +22,7 @@ int64_t strtoint64(const char *__restrict str, char **__restrict str_end,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<char *>(str + result.parsed_len);
 
   return result;
@@ -34,7 +34,7 @@ uint64_t strtouint64(const char *__restrict str, char **__restrict str_end,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<char *>(str + result.parsed_len);
 
   return result;

>From b33951bf840d2ad7ec79125213a66a1e3298f5d5 Mon Sep 17 00:00:00 2001
From: Alexey Samsonov <vonosmas at gmail.com>
Date: Thu, 28 May 2026 06:03:37 +0000
Subject: [PATCH 6/6] fix strtoull_l as well

---
 libc/src/stdlib/strtoull_l.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libc/src/stdlib/strtoull_l.cpp b/libc/src/stdlib/strtoull_l.cpp
index 9eb056b0e59b4..b82b5bb50b82d 100644
--- a/libc/src/stdlib/strtoull_l.cpp
+++ b/libc/src/stdlib/strtoull_l.cpp
@@ -21,7 +21,7 @@ LLVM_LIBC_FUNCTION(unsigned long long, strtoull_l,
   if (result.has_error())
     libc_errno = result.error;
 
-  if (str_end != nullptr)
+  if (str_end != nullptr && result.error != EINVAL)
     *str_end = const_cast<char *>(str + result.parsed_len);
 
   return result;



More information about the libc-commits mailing list