https://github.com/vhscampos updated 
https://github.com/llvm/llvm-project/pull/197709

>From b36b993f11256c25023c9f6cf780de85ae42a42f Mon Sep 17 00:00:00 2001
From: Victor Campos <[email protected]>
Date: Mon, 23 Mar 2026 10:22:57 +0000
Subject: [PATCH 1/4] [libc] Annex K: strcpy_s

This patch adds Annex K's `strcpy_s`.
---
 libc/config/baremetal/aarch64/entrypoints.txt |   1 +
 libc/config/baremetal/arm/entrypoints.txt     |   1 +
 libc/config/linux/aarch64/entrypoints.txt     |   1 +
 libc/config/linux/arm/entrypoints.txt         |   1 +
 libc/config/linux/x86_64/entrypoints.txt      |   1 +
 libc/include/string.yaml                      |   9 ++
 libc/src/string/CMakeLists.txt                |  19 +++
 libc/src/string/strcpy_s.cpp                  |  74 ++++++++++
 libc/src/string/strcpy_s.h                    |  27 ++++
 libc/test/src/string/CMakeLists.txt           |  13 ++
 libc/test/src/string/strcpy_s_test.cpp        | 138 ++++++++++++++++++
 11 files changed, 285 insertions(+)
 create mode 100644 libc/src/string/strcpy_s.cpp
 create mode 100644 libc/src/string/strcpy_s.h
 create mode 100644 libc/test/src/string/strcpy_s_test.cpp

diff --git a/libc/config/baremetal/aarch64/entrypoints.txt 
b/libc/config/baremetal/aarch64/entrypoints.txt
index 452abd985b3a5..110538f8b6be5 100644
--- a/libc/config/baremetal/aarch64/entrypoints.txt
+++ b/libc/config/baremetal/aarch64/entrypoints.txt
@@ -75,6 +75,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.string.strcmp
     libc.src.string.strcoll
     libc.src.string.strcpy
+    libc.src.string.strcpy_s
     libc.src.string.strcspn
     libc.src.string.strdup
     libc.src.string.strerror
diff --git a/libc/config/baremetal/arm/entrypoints.txt 
b/libc/config/baremetal/arm/entrypoints.txt
index 41c80efc64227..69d13fa293e6e 100644
--- a/libc/config/baremetal/arm/entrypoints.txt
+++ b/libc/config/baremetal/arm/entrypoints.txt
@@ -75,6 +75,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.string.strcmp
     libc.src.string.strcoll
     libc.src.string.strcpy
+    libc.src.string.strcpy_s
     libc.src.string.strcspn
     libc.src.string.strdup
     libc.src.string.strerror
diff --git a/libc/config/linux/aarch64/entrypoints.txt 
b/libc/config/linux/aarch64/entrypoints.txt
index b7c9cabd934b4..30c3034fa7f28 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -68,6 +68,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.string.strcmp
     libc.src.string.strcoll
     libc.src.string.strcpy
+    libc.src.string.strcpy_s
     libc.src.string.strcspn
     libc.src.string.strdup
     libc.src.string.strerror
diff --git a/libc/config/linux/arm/entrypoints.txt 
b/libc/config/linux/arm/entrypoints.txt
index 906f36d45e337..f006c5476a6e0 100644
--- a/libc/config/linux/arm/entrypoints.txt
+++ b/libc/config/linux/arm/entrypoints.txt
@@ -41,6 +41,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.string.strchrnul
     libc.src.string.strcmp
     libc.src.string.strcpy
+    libc.src.string.strcpy_s
     libc.src.string.strcspn
     libc.src.string.strlcat
     libc.src.string.strlcpy
diff --git a/libc/config/linux/x86_64/entrypoints.txt 
b/libc/config/linux/x86_64/entrypoints.txt
index 9970f079abc08..dab5062d18d0c 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -70,6 +70,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.string.strcmp
     libc.src.string.strcoll
     libc.src.string.strcpy
+    libc.src.string.strcpy_s
     libc.src.string.strcspn
     libc.src.string.strdup
     libc.src.string.strerror
diff --git a/libc/include/string.yaml b/libc/include/string.yaml
index c0a96e58dbc94..234cc6e77cfd4 100644
--- a/libc/include/string.yaml
+++ b/libc/include/string.yaml
@@ -169,6 +169,15 @@ functions:
     arguments:
       - type: char *__restrict
       - type: const char *__restrict
+  - name: strcpy_s
+    standards:
+      - stdc
+    return_type: errno_t
+    arguments:
+      - type: char *__restrict
+      - type: rsize_t
+      - type: const char *__restrict
+    guard: LIBC_HAS_ANNEX_K
   - name: strcspn
     standards:
       - stdc
diff --git a/libc/src/string/CMakeLists.txt b/libc/src/string/CMakeLists.txt
index e28fe7af8cf25..accf30e865ad9 100644
--- a/libc/src/string/CMakeLists.txt
+++ b/libc/src/string/CMakeLists.txt
@@ -172,6 +172,25 @@ add_entrypoint_object(
     .string_utils
 )
 
+add_entrypoint_object(
+  strcpy_s
+  SRCS
+    strcpy_s.cpp
+  HDRS
+    strcpy_s.h
+  DEPENDS
+    .memory_utils.inline_memcpy
+    .string_utils
+    .strnlen_s
+    libc.src.__support.common
+    libc.src.__support.constraint_handler
+    libc.src.__support.macros.config
+    libc.hdr.types.constraint_handler_t
+    libc.hdr.types.errno_t
+    libc.hdr.types.rsize_t
+    libc.hdr.stdint_proxy
+)
+
 add_entrypoint_object(
   strcspn
   SRCS
diff --git a/libc/src/string/strcpy_s.cpp b/libc/src/string/strcpy_s.cpp
new file mode 100644
index 0000000000000..00d946dfa1682
--- /dev/null
+++ b/libc/src/string/strcpy_s.cpp
@@ -0,0 +1,74 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// This file contains the implementation of strcpy_s.
+///
+//===----------------------------------------------------------------------===//
+
+// For RSIZE_MAX
+#define __STDC_WANT_LIB_EXT1__ 1
+#include "hdr/stdint_proxy.h"
+#undef __STDC_WANT_LIB_EXT1__
+
+#include "hdr/types/constraint_handler_t.h"
+#include "hdr/types/errno_t.h"
+#include "hdr/types/rsize_t.h"
+#include "src/__support/common.h"
+#include "src/__support/constraint_handler.h"
+#include "src/__support/macros/config.h"
+#include "src/string/memory_utils/inline_memcpy.h"
+#include "src/string/strcpy_s.h"
+#include "src/string/string_utils.h"
+#include "src/string/strnlen_s.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(errno_t, strcpy_s,
+                   (char *__restrict s1, rsize_t s1max,
+                    const char *__restrict s2)) {
+  const char *constraint_violation_msg = 0;
+  size_t count;
+
+  if (s1 == 0) {
+    constraint_violation_msg = "strcpy_s: s1 cannot be null";
+  } else if (s2 == 0) {
+    constraint_violation_msg = "strcpy_s: s2 cannot be null";
+  } else if (s1max > RSIZE_MAX) {
+    constraint_violation_msg = "strcpy_s: s1max cannot exceed RSIZE_MAX";
+  } else if (s1max == 0) {
+    constraint_violation_msg = "strcpy_s: s1max cannot be 0";
+  } else if (count = strnlen_s(s2, s1max);
+             s1max == count) { // count can't be greater than s1max by
+                               // definition, so no need to check for this case
+    constraint_violation_msg = "strcpy_s: s1max is too small for s2";
+  }
+  // Check overlap using the full regions defined by the standard, including 
the
+  // terminating null byte:
+  //   destination: [s1, s1 + s1max)
+  //   source:      [s2, s2 + count + 1)
+  // Use s1max for the destination's length, not count + 1, because the
+  // standard allows for overwriting the entire destination region, even if
+  // s2's length is smaller than s1max.
+  else if (s2 < (s1 + s1max) && s1 < (s2 + count + 1)) {
+    constraint_violation_msg = "strcpy_s: s1 and s2 cannot overlap";
+  }
+
+  if (constraint_violation_msg) {
+    if (s1 != 0 && s1max > 0 && s1max <= RSIZE_MAX) {
+      s1[0] = '\0';
+    }
+    libc_global_constraint_handler(constraint_violation_msg, 0, 1);
+    return 1;
+  }
+
+  inline_memcpy(s1, s2, count + 1);
+  return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/string/strcpy_s.h b/libc/src/string/strcpy_s.h
new file mode 100644
index 0000000000000..2c8e0751d1425
--- /dev/null
+++ b/libc/src/string/strcpy_s.h
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// This file contains the implementation header for strcpy_s.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STRING_STRCPY_S_H
+#define LLVM_LIBC_SRC_STRING_STRCPY_S_H
+
+#include "hdr/types/errno_t.h"
+#include "hdr/types/rsize_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+errno_t strcpy_s(char *__restrict s1, rsize_t s1max, const char *__restrict 
s2);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STRING_STRCPY_S_H
diff --git a/libc/test/src/string/CMakeLists.txt 
b/libc/test/src/string/CMakeLists.txt
index 17927ea93ed1e..e79d8e6257f5d 100644
--- a/libc/test/src/string/CMakeLists.txt
+++ b/libc/test/src/string/CMakeLists.txt
@@ -149,6 +149,19 @@ add_libc_test(
     libc.src.string.strcpy
 )
 
+add_libc_test(
+  strcpy_s_test
+  SUITE
+    libc-string-tests
+  SRCS
+    strcpy_s_test.cpp
+  DEPENDS
+    libc.src.string.strcpy_s
+    libc.hdr.stdint_proxy
+    libc.hdr.types.errno_t
+    libc.test.UnitTest.ConstraintHandlerCheckingTest
+)
+
 add_libc_test(
   strcspn_test
   SUITE
diff --git a/libc/test/src/string/strcpy_s_test.cpp 
b/libc/test/src/string/strcpy_s_test.cpp
new file mode 100644
index 0000000000000..ee0c820d9cb49
--- /dev/null
+++ b/libc/test/src/string/strcpy_s_test.cpp
@@ -0,0 +1,138 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// This file contains unit tests for strcpy_s.
+///
+//===----------------------------------------------------------------------===//
+
+#define __STDC_WANT_LIB_EXT1__ 1
+#include "hdr/stdint_proxy.h"
+#undef __STDC_WANT_LIB_EXT1__
+
+#include "hdr/types/errno_t.h"
+#include "src/string/strcpy_s.h"
+#include "test/UnitTest/ConstraintHandlerCheckingTest.h"
+
+using LlvmLibcStrCpySTest =
+    LIBC_NAMESPACE::testing::ConstraintHandlerCheckingTest;
+
+// Success cases
+TEST_F(LlvmLibcStrCpySTest, SuccessfulCopy) {
+  char s1[8];
+  const char *s2 = "abc";
+
+  ASSERT_FALSE(error_flag);
+  errno_t result = LIBC_NAMESPACE::strcpy_s(s1, sizeof(s1), s2);
+  ASSERT_EQ(result, 0);
+  ASSERT_STREQ(s1, s2);
+  ASSERT_FALSE(error_flag);
+}
+
+TEST_F(LlvmLibcStrCpySTest, ExactFitSuccessfulCopy) {
+  char s1[4];
+  const char *s2 = "abc";
+
+  ASSERT_FALSE(error_flag);
+  errno_t result = LIBC_NAMESPACE::strcpy_s(s1, sizeof(s1), s2);
+  ASSERT_EQ(result, 0);
+  ASSERT_STREQ(s1, s2);
+  ASSERT_FALSE(error_flag);
+}
+
+TEST_F(LlvmLibcStrCpySTest, AdjacentObjectsDoNotOverlap) {
+  char s[8] = {'a', 'b', 'c', '\0', '?', '?', '?', '?'};
+
+  ASSERT_FALSE(error_flag);
+  errno_t result = LIBC_NAMESPACE::strcpy_s(s + 4, 4, s);
+  ASSERT_EQ(result, 0);
+  ASSERT_STREQ(s + 4, "abc");
+  ASSERT_FALSE(error_flag);
+}
+
+TEST_F(LlvmLibcStrCpySTest, EmptySourceString) {
+  char s1[4];
+
+  ASSERT_FALSE(error_flag);
+  errno_t result = LIBC_NAMESPACE::strcpy_s(s1, sizeof(s1), "");
+  ASSERT_EQ(result, 0);
+  ASSERT_EQ(s1[0], '\0');
+  ASSERT_FALSE(error_flag);
+}
+
+// Failure cases
+TEST_F(LlvmLibcStrCpySTest, NullS1) {
+  const char *s2 = "abc";
+  char *s1 = 0;
+
+  ASSERT_FALSE(error_flag);
+  errno_t result = LIBC_NAMESPACE::strcpy_s(s1, 4, s2);
+  ASSERT_NE(result, 0);
+  ASSERT_TRUE(error_flag);
+}
+
+TEST_F(LlvmLibcStrCpySTest, NullS2) {
+  char s1[4];
+  const char *s2 = 0;
+
+  ASSERT_FALSE(error_flag);
+  errno_t result = LIBC_NAMESPACE::strcpy_s(s1, 4, s2);
+  ASSERT_NE(result, 0);
+  ASSERT_EQ(s1[0], '\0');
+  ASSERT_TRUE(error_flag);
+}
+
+TEST_F(LlvmLibcStrCpySTest, S1MaxGreaterThanRSizeMax) {
+  char s1[4];
+  const char *s2 = "abc";
+
+  ASSERT_FALSE(error_flag);
+  errno_t result = LIBC_NAMESPACE::strcpy_s(s1, RSIZE_MAX + 1, s2);
+  ASSERT_NE(result, 0);
+  ASSERT_TRUE(error_flag);
+}
+
+TEST_F(LlvmLibcStrCpySTest, S1MaxIsZero) {
+  char s1[4];
+  const char *s2 = "abc";
+
+  ASSERT_FALSE(error_flag);
+  errno_t result = LIBC_NAMESPACE::strcpy_s(s1, 0, s2);
+  ASSERT_NE(result, 0);
+  ASSERT_TRUE(error_flag);
+}
+
+TEST_F(LlvmLibcStrCpySTest, S1MaxTooSmallForS2) {
+  char s1[3];
+  const char *s2 = "abc";
+
+  ASSERT_FALSE(error_flag);
+  errno_t result = LIBC_NAMESPACE::strcpy_s(s1, 3, s2);
+  ASSERT_NE(result, 0);
+  ASSERT_EQ(s1[0], '\0');
+  ASSERT_TRUE(error_flag);
+
+  s2 = "abcd";
+  s1[0] = '?';
+  error_flag = false;
+  ASSERT_FALSE(error_flag);
+  result = LIBC_NAMESPACE::strcpy_s(s1, 3, s2);
+  ASSERT_NE(result, 0);
+  ASSERT_EQ(s1[0], '\0');
+  ASSERT_TRUE(error_flag);
+}
+
+TEST_F(LlvmLibcStrCpySTest, OverlappingObjects) {
+  char s[10] = "123456789";
+
+  ASSERT_FALSE(error_flag);
+  errno_t result = LIBC_NAMESPACE::strcpy_s(s, 6, s + 4);
+  ASSERT_NE(result, 0);
+  ASSERT_EQ(s[0], '\0');
+  ASSERT_TRUE(error_flag);
+}

>From b54d6afb18e772d0ee4aaff63390c535880d1f7b Mon Sep 17 00:00:00 2001
From: Victor Campos <[email protected]>
Date: Wed, 1 Jul 2026 15:21:14 +0100
Subject: [PATCH 2/4] Protect overlap check against overflow and add some
 missing entrypoints

---
 libc/config/darwin/aarch64/entrypoints.txt | 1 +
 libc/config/darwin/x86_64/entrypoints.txt  | 1 +
 libc/src/string/strcpy_s.cpp               | 7 ++++---
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/libc/config/darwin/aarch64/entrypoints.txt 
b/libc/config/darwin/aarch64/entrypoints.txt
index 914d2b7918da1..4b14f9f28aace 100644
--- a/libc/config/darwin/aarch64/entrypoints.txt
+++ b/libc/config/darwin/aarch64/entrypoints.txt
@@ -38,6 +38,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.string.strchrnul
     libc.src.string.strcmp
     libc.src.string.strcpy
+    libc.src.string.strcpy_s
     libc.src.string.strcspn
     libc.src.string.strlcat
     libc.src.string.strlcpy
diff --git a/libc/config/darwin/x86_64/entrypoints.txt 
b/libc/config/darwin/x86_64/entrypoints.txt
index b941c968255e8..14718b71dbd2a 100644
--- a/libc/config/darwin/x86_64/entrypoints.txt
+++ b/libc/config/darwin/x86_64/entrypoints.txt
@@ -37,6 +37,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.string.strchrnul
     libc.src.string.strcmp
     libc.src.string.strcpy
+    libc.src.string.strcpy_s
     libc.src.string.strcspn
     libc.src.string.strlcat
     libc.src.string.strlcpy
diff --git a/libc/src/string/strcpy_s.cpp b/libc/src/string/strcpy_s.cpp
index 00d946dfa1682..f9bf2d5fd480e 100644
--- a/libc/src/string/strcpy_s.cpp
+++ b/libc/src/string/strcpy_s.cpp
@@ -11,11 +11,9 @@
 ///
 
//===----------------------------------------------------------------------===//
 
-// For RSIZE_MAX
 #define __STDC_WANT_LIB_EXT1__ 1
 #include "hdr/stdint_proxy.h"
 #undef __STDC_WANT_LIB_EXT1__
-
 #include "hdr/types/constraint_handler_t.h"
 #include "hdr/types/errno_t.h"
 #include "hdr/types/rsize_t.h"
@@ -55,7 +53,10 @@ LLVM_LIBC_FUNCTION(errno_t, strcpy_s,
   // Use s1max for the destination's length, not count + 1, because the
   // standard allows for overwriting the entire destination region, even if
   // s2's length is smaller than s1max.
-  else if (s2 < (s1 + s1max) && s1 < (s2 + count + 1)) {
+  else if (const uintptr_t s1_addr = reinterpret_cast<uintptr_t>(s1),
+           s2_addr = reinterpret_cast<uintptr_t>(s2);
+           s1_addr < s2_addr ? s2_addr - s1_addr < s1max
+                             : s1_addr - s2_addr < count + 1) {
     constraint_violation_msg = "strcpy_s: s1 and s2 cannot overlap";
   }
 

>From 4f2222e8e1e93a82758e627376f72f91c843ca68 Mon Sep 17 00:00:00 2001
From: Victor Campos <[email protected]>
Date: Fri, 3 Jul 2026 13:51:53 +0100
Subject: [PATCH 3/4] Address comments

---
 .../include/llvm-libc-macros/annex-k-macros.h |  9 ++-
 libc/src/string/strcpy_s.cpp                  | 68 ++++++++++---------
 libc/src/string/string_utils.h                |  4 ++
 libc/src/string/strnlen_s.cpp                 |  2 +-
 4 files changed, 50 insertions(+), 33 deletions(-)

diff --git a/libc/include/llvm-libc-macros/annex-k-macros.h 
b/libc/include/llvm-libc-macros/annex-k-macros.h
index 7cfb5c1d3060b..bc7604d78c5ca 100644
--- a/libc/include/llvm-libc-macros/annex-k-macros.h
+++ b/libc/include/llvm-libc-macros/annex-k-macros.h
@@ -1,10 +1,15 @@
-//===-- Definition of macros to be used with Annex K functions 
------------===//
+//===----------------------------------------------------------------------===//
 //
 // 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
+/// This file defines macros used with Annex K functions.
+///
+//===----------------------------------------------------------------------===//
 
 #ifndef LLVM_LIBC_INCLUDE_LLVM_LIBC_MACROS_ANNEX_K_MACROS_H
 #define LLVM_LIBC_INCLUDE_LLVM_LIBC_MACROS_ANNEX_K_MACROS_H
@@ -20,6 +25,8 @@
 
 #define LIBC_HAS_ANNEX_K
 
+#define LIBC_ERRNO_T_ERROR_VALUE 1
+
 #endif // defined(__STDC_WANT_LIB_EXT1__) && __STDC_WANT_LIB_EXT1__ == 1
 
 #endif // (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) ||
diff --git a/libc/src/string/strcpy_s.cpp b/libc/src/string/strcpy_s.cpp
index f9bf2d5fd480e..6897ebd89b7df 100644
--- a/libc/src/string/strcpy_s.cpp
+++ b/libc/src/string/strcpy_s.cpp
@@ -11,41 +11,39 @@
 ///
 
//===----------------------------------------------------------------------===//
 
+#include "src/string/strcpy_s.h"
 #define __STDC_WANT_LIB_EXT1__ 1
 #include "hdr/stdint_proxy.h"
 #undef __STDC_WANT_LIB_EXT1__
 #include "hdr/types/constraint_handler_t.h"
 #include "hdr/types/errno_t.h"
 #include "hdr/types/rsize_t.h"
+#include "src/__support/CPP/expected.h"
 #include "src/__support/common.h"
 #include "src/__support/constraint_handler.h"
 #include "src/__support/macros/config.h"
 #include "src/string/memory_utils/inline_memcpy.h"
-#include "src/string/strcpy_s.h"
 #include "src/string/string_utils.h"
-#include "src/string/strnlen_s.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
-LLVM_LIBC_FUNCTION(errno_t, strcpy_s,
-                   (char *__restrict s1, rsize_t s1max,
-                    const char *__restrict s2)) {
-  const char *constraint_violation_msg = 0;
-  size_t count;
+LIBC_INLINE static cpp::expected<size_t, const char *>
+validate(char *__restrict s1, rsize_t s1max, const char *__restrict s2) {
+  if (s1 == nullptr)
+    return cpp::unexpected("strcpy_s: s1 cannot be null");
+  if (s2 == nullptr)
+    return cpp::unexpected("strcpy_s: s2 cannot be null");
+  if (s1max > RSIZE_MAX)
+    return cpp::unexpected("strcpy_s: s1max cannot exceed RSIZE_MAX");
+  if (s1max == 0)
+    return cpp::unexpected("strcpy_s: s1max cannot be 0");
+
+  // count can't be greater than s1max by definition, so no need to check for
+  // this case.
+  size_t count = internal::strnlen_s(s2, s1max);
+  if (s1max == count)
+    return cpp::unexpected("strcpy_s: s1max is too small for s2");
 
-  if (s1 == 0) {
-    constraint_violation_msg = "strcpy_s: s1 cannot be null";
-  } else if (s2 == 0) {
-    constraint_violation_msg = "strcpy_s: s2 cannot be null";
-  } else if (s1max > RSIZE_MAX) {
-    constraint_violation_msg = "strcpy_s: s1max cannot exceed RSIZE_MAX";
-  } else if (s1max == 0) {
-    constraint_violation_msg = "strcpy_s: s1max cannot be 0";
-  } else if (count = strnlen_s(s2, s1max);
-             s1max == count) { // count can't be greater than s1max by
-                               // definition, so no need to check for this case
-    constraint_violation_msg = "strcpy_s: s1max is too small for s2";
-  }
   // Check overlap using the full regions defined by the standard, including 
the
   // terminating null byte:
   //   destination: [s1, s1 + s1max)
@@ -53,22 +51,30 @@ LLVM_LIBC_FUNCTION(errno_t, strcpy_s,
   // Use s1max for the destination's length, not count + 1, because the
   // standard allows for overwriting the entire destination region, even if
   // s2's length is smaller than s1max.
-  else if (const uintptr_t s1_addr = reinterpret_cast<uintptr_t>(s1),
-           s2_addr = reinterpret_cast<uintptr_t>(s2);
-           s1_addr < s2_addr ? s2_addr - s1_addr < s1max
-                             : s1_addr - s2_addr < count + 1) {
-    constraint_violation_msg = "strcpy_s: s1 and s2 cannot overlap";
-  }
+  const uintptr_t s1_addr = reinterpret_cast<uintptr_t>(s1);
+  const uintptr_t s2_addr = reinterpret_cast<uintptr_t>(s2);
+  if (s1_addr < s2_addr ? s2_addr - s1_addr < s1max
+                        : s1_addr - s2_addr < count + 1)
+    return cpp::unexpected("strcpy_s: s1 and s2 cannot overlap");
+
+  return count;
+}
+
+LLVM_LIBC_FUNCTION(errno_t, strcpy_s,
+                   (char *__restrict s1, rsize_t s1max,
+                    const char *__restrict s2)) {
+  const auto count_expected = validate(s1, s1max, s2);
 
-  if (constraint_violation_msg) {
-    if (s1 != 0 && s1max > 0 && s1max <= RSIZE_MAX) {
+  if (!count_expected.has_value()) {
+    if (s1 != nullptr && s1max > 0 && s1max <= RSIZE_MAX) {
       s1[0] = '\0';
     }
-    libc_global_constraint_handler(constraint_violation_msg, 0, 1);
-    return 1;
+    libc_global_constraint_handler(count_expected.error(), 0,
+                                   LIBC_ERRNO_T_ERROR_VALUE);
+    return LIBC_ERRNO_T_ERROR_VALUE;
   }
 
-  inline_memcpy(s1, s2, count + 1);
+  inline_memcpy(s1, s2, count_expected.value() + 1);
   return 0;
 }
 
diff --git a/libc/src/string/string_utils.h b/libc/src/string/string_utils.h
index 8c8879b4886e8..758ba9623124e 100644
--- a/libc/src/string/string_utils.h
+++ b/libc/src/string/string_utils.h
@@ -130,6 +130,10 @@ LIBC_INLINE size_t strnlen(const char *s, size_t max_len) {
   return temp ? reinterpret_cast<const char *>(temp) - s : max_len;
 }
 
+LIBC_INLINE size_t strnlen_s(const char *s, size_t n) {
+  return (s != nullptr) ? internal::strnlen(s, n) : 0;
+}
+
 } // namespace internal
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/src/string/strnlen_s.cpp b/libc/src/string/strnlen_s.cpp
index dab0dec77efd8..b19a3d0a97244 100644
--- a/libc/src/string/strnlen_s.cpp
+++ b/libc/src/string/strnlen_s.cpp
@@ -15,7 +15,7 @@
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(size_t, strnlen_s, (const char *s, size_t n)) {
-  return (s != 0) ? internal::strnlen(s, n) : 0;
+  return internal::strnlen_s(s, n);
 }
 
 } // namespace LIBC_NAMESPACE_DECL

>From 722c030a7d640ac3d3ca35b6b618f59983ac0885 Mon Sep 17 00:00:00 2001
From: Victor Campos <[email protected]>
Date: Fri, 3 Jul 2026 14:18:08 +0100
Subject: [PATCH 4/4] Fix CMakeLists dependencies

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

diff --git a/libc/src/string/CMakeLists.txt b/libc/src/string/CMakeLists.txt
index accf30e865ad9..90d761c42bb27 100644
--- a/libc/src/string/CMakeLists.txt
+++ b/libc/src/string/CMakeLists.txt
@@ -181,9 +181,9 @@ add_entrypoint_object(
   DEPENDS
     .memory_utils.inline_memcpy
     .string_utils
-    .strnlen_s
     libc.src.__support.common
     libc.src.__support.constraint_handler
+    libc.src.__support.CPP.expected
     libc.src.__support.macros.config
     libc.hdr.types.constraint_handler_t
     libc.hdr.types.errno_t

_______________________________________________
llvm-branch-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits

Reply via email to