[libc-commits] [libc] 934e561 - [libc] Don't touch str_end in strto* and wcsto* functions when base is incorrect (#200073)
via libc-commits
libc-commits at lists.llvm.org
Thu May 28 10:05:33 PDT 2026
Author: Alexey Samsonov
Date: 2026-05-28T10:05:27-07:00
New Revision: 934e5610ebfae936598896dc188cad331992d1af
URL: https://github.com/llvm/llvm-project/commit/934e5610ebfae936598896dc188cad331992d1af
DIFF: https://github.com/llvm/llvm-project/commit/934e5610ebfae936598896dc188cad331992d1af.diff
LOG: [libc] Don't touch str_end in strto* and wcsto* functions when base is incorrect (#200073)
Updates the behavior of `stro*` and `wcsto*` endpoints to not touch
`str_end` pointer when the provided value of `base` is incorrect and
errno is set to `EINVAL`.
`strto*` and `wcsto*` functions accept `base` as an input argument,
which can only be 0 or lie in [2,36] range. In case of invalid argument,
the functions should return 0 and set errno accordingly. Should the
"output argument" of `str_end` be updated in this case to point to the
beginning of the input string?
* C standard is unclear about it -
is says that `str` should be stored in `str_end` **if `str` is empty or
does not have the expected form** -- but there is no "expected form" for
invalid base values.
* POSIX standard explicitly mentions that for incorrect base value errno
should be set to `EINVAL` and points out that the value of `str_end`
is **unspecified** in this case.
Known implementations don't have a universal agreement about it:
* musl sets `str_end` unconditionally
* glibc doesn't do that in `EINVAL` case
* newlib doesn't do that either.
LLVM-libc used to unconditionally set `str_end` even in `EINVAL` case,
which is changed in this PR. This would not only help with portability,
but makes sense by itself - if input arguments are clearly invalid,
let's not touch output arguments either.
Assisted by: Gemini, human-reviewed
Added:
Modified:
libc/src/inttypes/strtoimax.cpp
libc/src/inttypes/strtoumax.cpp
libc/src/stdlib/strtol.cpp
libc/src/stdlib/strtol_l.cpp
libc/src/stdlib/strtoll.cpp
libc/src/stdlib/strtoll_l.cpp
libc/src/stdlib/strtoul.cpp
libc/src/stdlib/strtoul_l.cpp
libc/src/stdlib/strtoull.cpp
libc/src/stdlib/strtoull_l.cpp
libc/src/wchar/wcstol.cpp
libc/src/wchar/wcstoll.cpp
libc/src/wchar/wcstoul.cpp
libc/src/wchar/wcstoull.cpp
libc/test/src/stdlib/StrtolTest.h
libc/test/src/stdlib/strtoint32_test.cpp
libc/test/src/stdlib/strtoint64_test.cpp
libc/test/src/wchar/WcstolTest.h
Removed:
################################################################################
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/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;
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/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..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;
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) {
More information about the libc-commits
mailing list