https://github.com/jpjepko updated 
https://github.com/llvm/llvm-project/pull/183004

>From d9f4729f5e242e8e8808bdad7b535cb2b6512796 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Mon, 23 Feb 2026 21:31:37 +0100
Subject: [PATCH 01/11] Initial refactor commit

We need to use the lambdas in checkFortifiedBuiltinMemoryFunction. This
commit refactors the lambdas into a helper class so they can be used by
other functions.
---
 clang/lib/Sema/SemaChecking.cpp | 128 +++++++++++++++++++-------------
 1 file changed, 75 insertions(+), 53 deletions(-)

diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 0ea41ff1f613e..a087f3008eada 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1141,31 +1141,19 @@ static bool ProcessFormatStringLiteral(const Expr 
*FormatExpr,
   return false;
 }
 
-void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
-                                               CallExpr *TheCall) {
-  if (TheCall->isValueDependent() || TheCall->isTypeDependent() ||
-      isConstantEvaluatedContext())
-    return;
-
-  bool UseDABAttr = false;
-  const FunctionDecl *UseDecl = FD;
-
-  const auto *DABAttr = FD->getAttr<DiagnoseAsBuiltinAttr>();
-  if (DABAttr) {
-    UseDecl = DABAttr->getFunction();
-    assert(UseDecl && "Missing FunctionDecl in DiagnoseAsBuiltin attribute!");
-    UseDABAttr = true;
+namespace {
+/// Helper class for buffer overflow/overread checking in fortified functions.
+class FortifiedBufferChecker {
+public:
+  FortifiedBufferChecker(Sema &S, FunctionDecl *FD, CallExpr *TheCall)
+      : S(S), TheCall(TheCall), FD(FD),
+        DABAttr(FD ? FD->getAttr<DiagnoseAsBuiltinAttr>() : nullptr),
+        UseDABAttr(DABAttr != nullptr) {
+    const TargetInfo &TI = S.getASTContext().getTargetInfo();
+    SizeTypeWidth = TI.getTypeWidth(TI.getSizeType());
   }
 
-  unsigned BuiltinID = UseDecl->getBuiltinID(/*ConsiderWrappers=*/true);
-
-  if (!BuiltinID)
-    return;
-
-  const TargetInfo &TI = getASTContext().getTargetInfo();
-  unsigned SizeTypeWidth = TI.getTypeWidth(TI.getSizeType());
-
-  auto TranslateIndex = [&](unsigned Index) -> std::optional<unsigned> {
+  std::optional<unsigned> TranslateIndex(unsigned Index) {
     // If we refer to a diagnose_as_builtin attribute, we need to change the
     // argument index to refer to the arguments of the called function. Unless
     // the index is out of bounds, which presumably means it's a variadic
@@ -1179,25 +1167,24 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
     if (NewIndex >= TheCall->getNumArgs())
       return std::nullopt;
     return NewIndex;
-  };
+  }
 
-  auto ComputeExplicitObjectSizeArgument =
-      [&](unsigned Index) -> std::optional<llvm::APSInt> {
+  std::optional<llvm::APSInt>
+  ComputeExplicitObjectSizeArgument(unsigned Index) {
     std::optional<unsigned> IndexOptional = TranslateIndex(Index);
     if (!IndexOptional)
       return std::nullopt;
     unsigned NewIndex = *IndexOptional;
     Expr::EvalResult Result;
     Expr *SizeArg = TheCall->getArg(NewIndex);
-    if (!SizeArg->EvaluateAsInt(Result, getASTContext()))
+    if (!SizeArg->EvaluateAsInt(Result, S.getASTContext()))
       return std::nullopt;
     llvm::APSInt Integer = Result.Val.getInt();
     Integer.setIsUnsigned(true);
     return Integer;
-  };
+  }
 
-  auto ComputeSizeArgument =
-      [&](unsigned Index) -> std::optional<llvm::APSInt> {
+  std::optional<llvm::APSInt> ComputeSizeArgument(unsigned Index) {
     // If the parameter has a pass_object_size attribute, then we should use 
its
     // (potentially) more strict checking mode. Otherwise, conservatively 
assume
     // type 0.
@@ -1219,15 +1206,14 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
 
     const Expr *ObjArg = TheCall->getArg(NewIndex);
     if (std::optional<uint64_t> ObjSize =
-            ObjArg->tryEvaluateObjectSize(getASTContext(), BOSType)) {
+            ObjArg->tryEvaluateObjectSize(S.getASTContext(), BOSType)) {
       // Get the object size in the target's size_t width.
       return llvm::APSInt::getUnsigned(*ObjSize).extOrTrunc(SizeTypeWidth);
     }
     return std::nullopt;
-  };
+  }
 
-  auto ComputeStrLenArgument =
-      [&](unsigned Index) -> std::optional<llvm::APSInt> {
+  std::optional<llvm::APSInt> ComputeStrLenArgument(unsigned Index) {
     std::optional<unsigned> IndexOptional = TranslateIndex(Index);
     if (!IndexOptional)
       return std::nullopt;
@@ -1236,12 +1222,45 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
     const Expr *ObjArg = TheCall->getArg(NewIndex);
 
     if (std::optional<uint64_t> Result =
-            ObjArg->tryEvaluateStrLen(getASTContext())) {
+            ObjArg->tryEvaluateStrLen(S.getASTContext())) {
       // Add 1 for null byte.
       return llvm::APSInt::getUnsigned(*Result + 1).extOrTrunc(SizeTypeWidth);
     }
     return std::nullopt;
-  };
+  }
+
+  const DiagnoseAsBuiltinAttr *getDABAttr() const { return DABAttr; }
+  unsigned getSizeTypeWidth() const { return SizeTypeWidth; }
+
+private:
+  Sema &S;
+  CallExpr *TheCall;
+  FunctionDecl *FD;
+  const DiagnoseAsBuiltinAttr *DABAttr;
+  bool UseDABAttr;
+  unsigned SizeTypeWidth;
+};
+} // anonymous namespace
+
+void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
+                                               CallExpr *TheCall) {
+  if (TheCall->isValueDependent() || TheCall->isTypeDependent() ||
+      isConstantEvaluatedContext())
+    return;
+
+  FortifiedBufferChecker Checker(*this, FD, TheCall);
+
+  const FunctionDecl *UseDecl = FD;
+  if (const auto *DABAttr = Checker.getDABAttr()) {
+    UseDecl = DABAttr->getFunction();
+    assert(UseDecl && "Missing FunctionDecl in DiagnoseAsBuiltin attribute!");
+  }
+
+  unsigned BuiltinID = UseDecl->getBuiltinID(/*ConsiderWrappers=*/true);
+  if (!BuiltinID)
+    return;
+
+  unsigned SizeTypeWidth = Checker.getSizeTypeWidth();
 
   std::optional<llvm::APSInt> SourceSize;
   std::optional<llvm::APSInt> DestinationSize;
@@ -1274,8 +1293,8 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
   case Builtin::BI__builtin_strcpy:
   case Builtin::BIstrcpy: {
     DiagID = diag::warn_fortify_strlen_overflow;
-    SourceSize = ComputeStrLenArgument(1);
-    DestinationSize = ComputeSizeArgument(0);
+    SourceSize = Checker.ComputeStrLenArgument(1);
+    DestinationSize = Checker.ComputeSizeArgument(0);
     break;
   }
 
@@ -1283,8 +1302,8 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
   case Builtin::BI__builtin___stpcpy_chk:
   case Builtin::BI__builtin___strcpy_chk: {
     DiagID = diag::warn_fortify_strlen_overflow;
-    SourceSize = ComputeStrLenArgument(1);
-    DestinationSize = ComputeExplicitObjectSizeArgument(2);
+    SourceSize = Checker.ComputeStrLenArgument(1);
+    DestinationSize = Checker.ComputeExplicitObjectSizeArgument(2);
     IsChkVariant = true;
     break;
   }
@@ -1318,7 +1337,7 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
     };
 
     auto ShiftedComputeSizeArgument = [&](unsigned Index) {
-      return ComputeSizeArgument(Index + DataIndex);
+      return Checker.ComputeSizeArgument(Index + DataIndex);
     };
     ScanfDiagnosticFormatHandler H(ShiftedComputeSizeArgument, Diagnose);
     const char *FormatBytes = FormatStrRef.data();
@@ -1351,10 +1370,10 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
         SourceSize = llvm::APSInt::getUnsigned(H.getSizeLowerBound())
                          .extOrTrunc(SizeTypeWidth);
         if (BuiltinID == Builtin::BI__builtin___sprintf_chk) {
-          DestinationSize = ComputeExplicitObjectSizeArgument(2);
+          DestinationSize = Checker.ComputeExplicitObjectSizeArgument(2);
           IsChkVariant = true;
         } else {
-          DestinationSize = ComputeSizeArgument(0);
+          DestinationSize = Checker.ComputeSizeArgument(0);
         }
         break;
       }
@@ -1372,9 +1391,10 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
   case Builtin::BI__builtin___memccpy_chk:
   case Builtin::BI__builtin___mempcpy_chk: {
     DiagID = diag::warn_builtin_chk_overflow;
-    SourceSize = ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 2);
+    SourceSize =
+        Checker.ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 2);
     DestinationSize =
-        ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1);
+        Checker.ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1);
     IsChkVariant = true;
     break;
   }
@@ -1382,8 +1402,8 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
   case Builtin::BI__builtin___snprintf_chk:
   case Builtin::BI__builtin___vsnprintf_chk: {
     DiagID = diag::warn_builtin_chk_overflow;
-    SourceSize = ComputeExplicitObjectSizeArgument(1);
-    DestinationSize = ComputeExplicitObjectSizeArgument(3);
+    SourceSize = Checker.ComputeExplicitObjectSizeArgument(1);
+    DestinationSize = Checker.ComputeExplicitObjectSizeArgument(3);
     IsChkVariant = true;
     break;
   }
@@ -1400,8 +1420,9 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
     // size larger than the destination buffer though; this is a runtime abort
     // in _FORTIFY_SOURCE mode, and is quite suspicious otherwise.
     DiagID = diag::warn_fortify_source_size_mismatch;
-    SourceSize = ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1);
-    DestinationSize = ComputeSizeArgument(0);
+    SourceSize =
+        Checker.ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1);
+    DestinationSize = Checker.ComputeSizeArgument(0);
     break;
   }
 
@@ -1414,8 +1435,9 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
   case Builtin::BImempcpy:
   case Builtin::BI__builtin_mempcpy: {
     DiagID = diag::warn_fortify_source_overflow;
-    SourceSize = ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1);
-    DestinationSize = ComputeSizeArgument(0);
+    SourceSize =
+        Checker.ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1);
+    DestinationSize = Checker.ComputeSizeArgument(0);
     break;
   }
   case Builtin::BIsnprintf:
@@ -1423,7 +1445,7 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
   case Builtin::BIvsnprintf:
   case Builtin::BI__builtin_vsnprintf: {
     DiagID = diag::warn_fortify_source_size_mismatch;
-    SourceSize = ComputeExplicitObjectSizeArgument(1);
+    SourceSize = Checker.ComputeExplicitObjectSizeArgument(1);
     const auto *FormatExpr = TheCall->getArg(2)->IgnoreParenImpCasts();
     StringRef FormatStrRef;
     size_t StrLen;
@@ -1452,7 +1474,7 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
         }
       }
     }
-    DestinationSize = ComputeSizeArgument(0);
+    DestinationSize = Checker.ComputeSizeArgument(0);
     const Expr *LenArg = TheCall->getArg(1)->IgnoreCasts();
     const Expr *Dest = TheCall->getArg(0)->IgnoreCasts();
     IdentifierInfo *FnInfo = FD->getIdentifier();

>From 40ed6b6e6c3f66a6263f9b717e817cb5b56c6b31 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Mon, 23 Feb 2026 22:38:16 +0100
Subject: [PATCH 02/11] Add overread checks for memcpy family of functions

Checks builtins and their _chk variants.
---
 clang/include/clang/Basic/DiagnosticGroups.td |   1 +
 .../clang/Basic/DiagnosticSemaKinds.td        |   4 +
 clang/include/clang/Sema/Sema.h               |   5 +
 clang/lib/Sema/SemaChecking.cpp               |  74 ++++++++++
 clang/test/Sema/warn-stringop-overread.c      | 138 ++++++++++++++++++
 5 files changed, 222 insertions(+)
 create mode 100644 clang/test/Sema/warn-stringop-overread.c

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 7df83d2a4011f..5fe83193d41d3 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1785,6 +1785,7 @@ def CrossTU : DiagGroup<"ctu">;
 def CTADMaybeUnsupported : DiagGroup<"ctad-maybe-unsupported">;
 
 def FortifySource : DiagGroup<"fortify-source", [FormatOverflow, 
FormatTruncation]>;
+def StringopOverread : DiagGroup<"stringop-overread">;
 
 def OverflowBehaviorAttributeIgnored
     : DiagGroup<"overflow-behavior-attribute-ignored">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 68016ec4d58a3..cbd5c69cf7060 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -956,6 +956,10 @@ def warn_fortify_source_size_mismatch : Warning<
   "'%0' size argument is too large; destination buffer has size %1,"
   " but size argument is %2">, InGroup<FortifySource>;
 
+def warn_stringop_overread
+    : Warning<"'%0' reading %1 byte%s1 from a region of size %2">,
+      InGroup<StringopOverread>;
+
 def warn_fortify_strlen_overflow: Warning<
   "'%0' will always overflow; destination buffer has size %1,"
   " but the source string has length %2 (including NUL byte)">,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 7fe4a386c7e04..d80c93efeb518 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2927,6 +2927,11 @@ class Sema final : public SemaBase {
       CallExpr *TheCall, EltwiseBuiltinArgTyRestriction ArgTyRestr =
                              EltwiseBuiltinArgTyRestriction::None);
 
+  /// Check for source buffer overread in memory functions.
+  void checkSourceBufferOverread(FunctionDecl *FD, CallExpr *TheCall,
+                                 unsigned SrcArgIdx, unsigned SizeArgIdx,
+                                 StringRef FunctionName = "");
+
 private:
   void CheckArrayAccess(const Expr *BaseExpr, const Expr *IndexExpr,
                         const ArraySubscriptExpr *ASE = nullptr,
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index a087f3008eada..d78f71340bd88 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1242,6 +1242,43 @@ class FortifiedBufferChecker {
 };
 } // anonymous namespace
 
+void Sema::checkSourceBufferOverread(FunctionDecl *FD, CallExpr *TheCall,
+                                     unsigned SrcArgIdx, unsigned SizeArgIdx,
+                                     StringRef FunctionName) {
+  if (TheCall->isValueDependent() || TheCall->isTypeDependent() ||
+      isConstantEvaluatedContext())
+    return;
+
+  FortifiedBufferChecker Checker(*this, FD, TheCall);
+
+  std::optional<llvm::APSInt> CopyLen =
+      Checker.ComputeExplicitObjectSizeArgument(SizeArgIdx);
+  std::optional<llvm::APSInt> SrcBufSize =
+      Checker.ComputeSizeArgument(SrcArgIdx);
+
+  if (!CopyLen || !SrcBufSize)
+    return;
+
+  // Warn only if copy length exceeds source buffer size.
+  if (llvm::APSInt::compareValues(*CopyLen, *SrcBufSize) <= 0)
+    return;
+
+  std::string FuncName;
+  if (FunctionName.empty()) {
+    if (const FunctionDecl *CalleeDecl = TheCall->getDirectCallee())
+      FuncName = CalleeDecl->getName().str();
+    else
+      FuncName = "memory function";
+  } else {
+    FuncName = FunctionName.str();
+  }
+
+  DiagRuntimeBehavior(TheCall->getBeginLoc(), TheCall,
+                      PDiag(diag::warn_stringop_overread)
+                          << FuncName << CopyLen->getZExtValue()
+                          << SrcBufSize->getZExtValue());
+}
+
 void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
                                                CallExpr *TheCall) {
   if (TheCall->isValueDependent() || TheCall->isTypeDependent() ||
@@ -1396,6 +1433,13 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
     DestinationSize =
         Checker.ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1);
     IsChkVariant = true;
+
+    if (BuiltinID == Builtin::BI__builtin___memcpy_chk ||
+        BuiltinID == Builtin::BI__builtin___memmove_chk ||
+        BuiltinID == Builtin::BI__builtin___mempcpy_chk) {
+      checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/1, /*SizeArgIdx=*/2,
+                                GetFunctionName());
+    }
     break;
   }
 
@@ -1438,8 +1482,38 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
     SourceSize =
         Checker.ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1);
     DestinationSize = Checker.ComputeSizeArgument(0);
+
+    // Buffer overread doesn't make sense for memset.
+    if (BuiltinID != Builtin::BImemset &&
+        BuiltinID != Builtin::BI__builtin_memset) {
+      checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/1, /*SizeArgIdx=*/2,
+                                GetFunctionName());
+    }
     break;
   }
+
+  // memchr(buf, val, size)
+  case Builtin::BImemchr:
+  case Builtin::BI__builtin_memchr: {
+    checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/0, /*SizeArgIdx=*/2,
+                              GetFunctionName());
+    return;
+  }
+
+  // memcmp/bcmp(buf0, buf1, size)
+  // Two checks since each buffer is read
+  case Builtin::BImemcmp:
+  case Builtin::BI__builtin_memcmp:
+  case Builtin::BIbcmp:
+  case Builtin::BI__builtin_bcmp: {
+    std::string Name = GetFunctionName();
+    checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/0, /*SizeArgIdx=*/2,
+                              Name);
+    checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/1, /*SizeArgIdx=*/2,
+                              Name);
+    return;
+  }
+
   case Builtin::BIsnprintf:
   case Builtin::BI__builtin_snprintf:
   case Builtin::BIvsnprintf:
diff --git a/clang/test/Sema/warn-stringop-overread.c 
b/clang/test/Sema/warn-stringop-overread.c
new file mode 100644
index 0000000000000..746684272729b
--- /dev/null
+++ b/clang/test/Sema/warn-stringop-overread.c
@@ -0,0 +1,138 @@
+// RUN: %clang_cc1 %s -verify
+// RUN: %clang_cc1 %s -verify -DUSE_BUILTINS
+
+typedef unsigned long size_t;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(USE_BUILTINS)
+#define memcpy(x,y,z) __builtin_memcpy(x,y,z)
+#define memmove(x,y,z) __builtin_memmove(x,y,z)
+#define memchr(x,y,z) __builtin_memchr(x,y,z)
+#define memcmp(x,y,z) __builtin_memcmp(x,y,z)
+#else
+void *memcpy(void *dst, const void *src, size_t c);
+void *memmove(void *dst, const void *src, size_t c);
+void *memchr(const void *s, int c, size_t n);
+int memcmp(const void *s1, const void *s2, size_t n);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+void test_memcpy_overread(void) {
+  char dst[100];
+  int src = 0;
+  memcpy(dst, &src, sizeof(src) + 1); // expected-warning {{'memcpy' reading 5 
bytes from a region of size 4}}
+}
+
+void test_memcpy_array_overread(void) {
+  int dest[10];
+  int src[5] = {1, 2, 3, 4, 5};
+  memcpy(dest, src, 10 * sizeof(int)); // expected-warning {{'memcpy' reading 
40 bytes from a region of size 20}}
+}
+
+void test_memcpy_struct_overread(void) {
+  struct S {
+    int x;
+    int y;
+  };
+  char dst[100];
+  struct S src = {1, 2};
+  memcpy(dst, &src, sizeof(struct S) + 1); // expected-warning {{'memcpy' 
reading 9 bytes from a region of size 8}}
+}
+
+void test_memmove_overread(void) {
+  char dst[100];
+  char src[10];
+  memmove(dst, src, 20); // expected-warning {{'memmove' reading 20 bytes from 
a region of size 10}}
+}
+
+void test_memcpy_no_warning_exact_size(void) {
+  char dst[100];
+  int src = 0;
+  memcpy(dst, &src, sizeof(src)); // no warning
+}
+
+void test_memcpy_no_warning_smaller_size(void) {
+  char dst[100];
+  int src[10];
+  memcpy(dst, src, 5 * sizeof(int)); // no warning
+}
+
+void test_memcpy_both_overflow(void) {
+  char dst[5];
+  int src = 0;
+  memcpy(dst, &src, 10); // expected-warning {{'memcpy' reading 10 bytes from 
a region of size 4}}
+                         // expected-warning@-1 {{'memcpy' will always 
overflow; destination buffer has size 5, but size argument is 10}}
+}
+
+void test_memchr_overread(void) {
+  char buf[4];
+  memchr(buf, 'a', 8); // expected-warning {{'memchr' reading 8 bytes from a 
region of size 4}}
+}
+
+void test_memchr_no_warning(void) {
+  char buf[10];
+  memchr(buf, 'a', 10); // no warning
+}
+
+void test_memcmp_overread_first(void) {
+  char a[4], b[100];
+  memcmp(a, b, 8); // expected-warning {{'memcmp' reading 8 bytes from a 
region of size 4}}
+}
+
+void test_memcmp_overread_second(void) {
+  char a[100], b[4];
+  memcmp(a, b, 8); // expected-warning {{'memcmp' reading 8 bytes from a 
region of size 4}}
+}
+
+void test_memcmp_overread_both(void) {
+  char a[4], b[2];
+  memcmp(a, b, 8); // expected-warning {{'memcmp' reading 8 bytes from a 
region of size 4}} \
+                    // expected-warning {{'memcmp' reading 8 bytes from a 
region of size 2}}
+}
+
+void test_memcmp_no_warning(void) {
+  char a[10], b[10];
+  memcmp(a, b, 10); // no warning
+}
+
+void test_memcpy_src_offset_overread(void) {
+  char src[] = {1, 2, 3, 4};
+  char dst[10];
+  memcpy(dst, src + 2, 3); // expected-warning {{'memcpy' reading 3 bytes from 
a region of size 2}}
+}
+
+void test_memcpy_src_offset_no_warning(void) {
+  char src[] = {1, 2, 3, 4};
+  char dst[10];
+  memcpy(dst, src + 2, 2); // no warning
+}
+
+int bcmp(const void *s1, const void *s2, size_t n);
+
+void test_bcmp_overread(void) {
+  char a[4], b[100];
+  bcmp(a, b, 8); // expected-warning {{'bcmp' reading 8 bytes from a region of 
size 4}}
+}
+
+void test_bcmp_no_warning(void) {
+  char a[10], b[10];
+  bcmp(a, b, 10); // no warning
+}
+
+void test_memcpy_chk_overread(void) {
+  char dst[100];
+  char src[4];
+  __builtin___memcpy_chk(dst, src, 8, sizeof(dst)); // expected-warning 
{{'memcpy' reading 8 bytes from a region of size 4}}
+}
+
+void test_memmove_chk_overread(void) {
+  char dst[100];
+  char src[4];
+  __builtin___memmove_chk(dst, src, 8, sizeof(dst)); // expected-warning 
{{'memmove' reading 8 bytes from a region of size 4}}
+}

>From 1236f3afcff374e8e3e80b0b3832c1e8496e4fba Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Tue, 24 Feb 2026 05:34:14 +0100
Subject: [PATCH 03/11] Fix findings in test suite

---
 clang/test/AST/ByteCode/builtin-functions.cpp | 20 +++++++++----------
 clang/test/Analysis/bstring.c                 |  4 ++++
 clang/test/Analysis/malloc.c                  |  1 +
 clang/test/Analysis/pr22954.c                 |  2 +-
 clang/test/Sema/builtin-memcpy.c              |  3 ++-
 clang/test/Sema/builtin-object-size.c         |  2 +-
 clang/test/Sema/warn-fortify-source.c         |  7 ++++---
 7 files changed, 23 insertions(+), 16 deletions(-)

diff --git a/clang/test/AST/ByteCode/builtin-functions.cpp 
b/clang/test/AST/ByteCode/builtin-functions.cpp
index 6c49d015c1184..91bdffb71f947 100644
--- a/clang/test/AST/ByteCode/builtin-functions.cpp
+++ b/clang/test/AST/ByteCode/builtin-functions.cpp
@@ -1,17 +1,17 @@
-// RUN: %clang_cc1 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter -triple x86_64 %s -verify=expected,both
-// RUN: %clang_cc1 -Wno-string-plus-int                                        
 -triple x86_64 %s -verify=ref,both
+// RUN: %clang_cc1 -Wno-string-plus-int -Wno-stringop-overread 
-fexperimental-new-constant-interpreter -triple x86_64 %s -verify=expected,both
+// RUN: %clang_cc1 -Wno-string-plus-int -Wno-stringop-overread                 
                        -triple x86_64 %s -verify=ref,both
 //
-// RUN: %clang_cc1 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter -triple i686 %s -verify=expected,both
-// RUN: %clang_cc1 -Wno-string-plus-int                                        
 -triple i686 %s -verify=ref,both
+// RUN: %clang_cc1 -Wno-string-plus-int -Wno-stringop-overread 
-fexperimental-new-constant-interpreter -triple i686 %s -verify=expected,both
+// RUN: %clang_cc1 -Wno-string-plus-int -Wno-stringop-overread                 
                        -triple i686 %s -verify=ref,both
 //
-// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter -triple x86_64 %s -verify=expected,both
-// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int                             
            -triple x86_64 %s -verify=ref,both
+// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int -Wno-stringop-overread 
-fexperimental-new-constant-interpreter -triple x86_64 %s -verify=expected,both
+// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int -Wno-stringop-overread      
                                   -triple x86_64 %s -verify=ref,both
 //
-// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter -triple i686 %s -verify=expected,both
-// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int                             
            -triple i686 %s -verify=ref,both
+// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int -Wno-stringop-overread 
-fexperimental-new-constant-interpreter -triple i686 %s -verify=expected,both
+// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int -Wno-stringop-overread      
                                   -triple i686 %s -verify=ref,both
 //
-// RUN: %clang_cc1 -triple avr -std=c++20 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter %s -verify=expected,both
-// RUN: %clang_cc1 -triple avr -std=c++20 -Wno-string-plus-int                 
                           -verify=ref,both %s
+// RUN: %clang_cc1 -triple avr -std=c++20 -Wno-string-plus-int 
-Wno-stringop-overread -fexperimental-new-constant-interpreter %s 
-verify=expected,both
+// RUN: %clang_cc1 -triple avr -std=c++20 -Wno-string-plus-int 
-Wno-stringop-overread                                            
-verify=ref,both %s
 
 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
 #define LITTLE_END 1
diff --git a/clang/test/Analysis/bstring.c b/clang/test/Analysis/bstring.c
index f015e0b5d9fb7..d4970489103b0 100644
--- a/clang/test/Analysis/bstring.c
+++ b/clang/test/Analysis/bstring.c
@@ -1,4 +1,5 @@
 // RUN: %clang_analyze_cc1 -verify %s \
+// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=unix.cstring \
 // RUN:   -analyzer-checker=alpha.unix.cstring \
@@ -7,6 +8,7 @@
 // RUN:   -analyzer-config eagerly-assume=false
 //
 // RUN: %clang_analyze_cc1 -verify %s -DUSE_BUILTINS \
+// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=unix.cstring \
 // RUN:   -analyzer-checker=alpha.unix.cstring \
@@ -15,6 +17,7 @@
 // RUN:   -analyzer-config eagerly-assume=false
 //
 // RUN: %clang_analyze_cc1 -verify %s -DVARIANT \
+// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=unix.cstring \
 // RUN:   -analyzer-checker=alpha.unix.cstring \
@@ -23,6 +26,7 @@
 // RUN:   -analyzer-config eagerly-assume=false
 //
 // RUN: %clang_analyze_cc1 -verify %s -DUSE_BUILTINS -DVARIANT \
+// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=unix.cstring \
 // RUN:   -analyzer-checker=alpha.unix.cstring \
diff --git a/clang/test/Analysis/malloc.c b/clang/test/Analysis/malloc.c
index 92b47bc3b5e9a..41e512c9a2a2e 100644
--- a/clang/test/Analysis/malloc.c
+++ b/clang/test/Analysis/malloc.c
@@ -1,5 +1,6 @@
 // RUN: %clang_analyze_cc1 -Wno-strict-prototypes -Wno-error=implicit-int 
-verify %s \
 // RUN:   -Wno-alloc-size \
+// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=alpha.deadcode.UnreachableCode \
 // RUN:   -analyzer-checker=unix \
diff --git a/clang/test/Analysis/pr22954.c b/clang/test/Analysis/pr22954.c
index 3d1cac1972066..b3910da6c70ab 100644
--- a/clang/test/Analysis/pr22954.c
+++ b/clang/test/Analysis/pr22954.c
@@ -3,7 +3,7 @@
 // At the moment the whole of the destination array content is invalidated.
 // If a.s1 region has a symbolic offset, the whole region of 'a' is 
invalidated.
 // Specific triple set to test structures of size 0.
-// RUN: %clang_analyze_cc1 -triple x86_64-pc-linux-gnu 
-analyzer-checker=core,unix.Malloc,debug.ExprInspection 
-Wno-error=int-conversion -verify -analyzer-config eagerly-assume=false %s
+// RUN: %clang_analyze_cc1 -triple x86_64-pc-linux-gnu 
-analyzer-checker=core,unix.Malloc,debug.ExprInspection 
-Wno-error=int-conversion -Wno-stringop-overread -verify -analyzer-config 
eagerly-assume=false %s
 
 typedef __typeof(sizeof(int)) size_t;
 
diff --git a/clang/test/Sema/builtin-memcpy.c b/clang/test/Sema/builtin-memcpy.c
index 2a55e78034a02..94f71e4c42a58 100644
--- a/clang/test/Sema/builtin-memcpy.c
+++ b/clang/test/Sema/builtin-memcpy.c
@@ -7,7 +7,8 @@
 /// Zero-sized structs should not crash.
 int b() {
   struct {      } a[10];
-  __builtin_memcpy(&a[2], a, 2); // c-warning {{buffer has size 0, but size 
argument is 2}}
+  __builtin_memcpy(&a[2], a, 2); // c-warning {{buffer has size 0, but size 
argument is 2}} \
+                                 // c-warning {{'memcpy' reading 2 bytes from 
a region of size 0}}
   return 0;
 }
 
diff --git a/clang/test/Sema/builtin-object-size.c 
b/clang/test/Sema/builtin-object-size.c
index a763c24fd6620..8d48d3f569d91 100644
--- a/clang/test/Sema/builtin-object-size.c
+++ b/clang/test/Sema/builtin-object-size.c
@@ -50,7 +50,7 @@ void f5(void)
 {
   char buf[10];
   memset((void *)0x100000000ULL, 0, 0x1000);
-  memcpy((char *)NULL + 0x10000, buf, 0x10);
+  memcpy((char *)NULL + 0x10000, buf, 0x10);  // expected-warning {{'memcpy' 
reading 16 bytes from a region of size 10}}
   memcpy1((char *)NULL + 0x10000, buf, 0x10); // expected-error {{argument 
value 4 is outside the valid range [0, 3]}}
 }
 
diff --git a/clang/test/Sema/warn-fortify-source.c 
b/clang/test/Sema/warn-fortify-source.c
index 750bd5361ade9..eaa326aa2a778 100644
--- a/clang/test/Sema/warn-fortify-source.c
+++ b/clang/test/Sema/warn-fortify-source.c
@@ -94,7 +94,7 @@ void call_stpcpy(void) {
 
 void call_memmove(void) {
   char s1[10], s2[20];
-  __builtin_memmove(s2, s1, 20);
+  __builtin_memmove(s2, s1, 20); // expected-warning {{'memmove' reading 20 
bytes from a region of size 10}}
   __builtin_memmove(s1, s2, 20); // expected-warning {{'memmove' will always 
overflow; destination buffer has size 10, but size argument is 20}}
 }
 
@@ -243,11 +243,12 @@ template <int A, int B>
 void call_memcpy_dep() {
   char bufferA[A];
   char bufferB[B];
-  memcpy(bufferA, bufferB, 10); // expected-warning{{'memcpy' will always 
overflow; destination buffer has size 9, but size argument is 10}}
+  memcpy(bufferA, bufferB, 10); // expected-warning{{'memcpy' will always 
overflow; destination buffer has size 9, but size argument is 10}} \
+                                // expected-warning{{'memcpy' reading 10 bytes 
from a region of size 9}}
 }
 
 void call_call_memcpy() {
-  call_memcpy_dep<10, 9>();
+  call_memcpy_dep<10, 9>(); // expected-note {{in instantiation of function 
template specialization 'call_memcpy_dep<10, 9>' requested here}}
   call_memcpy_dep<9, 10>(); // expected-note {{in instantiation of function 
template specialization 'call_memcpy_dep<9, 10>' requested here}}
 }
 #endif

>From e87afee666326ee8a8032bff65401cbf5f87a177 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Wed, 25 Feb 2026 21:43:55 +0100
Subject: [PATCH 04/11] Derive FuncName, remove UseDABAttr, clarify tests

---
 clang/include/clang/Sema/Sema.h       |  3 +-
 clang/lib/Sema/SemaChecking.cpp       | 54 ++++++++++++++-------------
 clang/test/Sema/warn-fortify-source.c |  5 ++-
 3 files changed, 33 insertions(+), 29 deletions(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index d80c93efeb518..784df4dd3c1a3 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2929,8 +2929,7 @@ class Sema final : public SemaBase {
 
   /// Check for source buffer overread in memory functions.
   void checkSourceBufferOverread(FunctionDecl *FD, CallExpr *TheCall,
-                                 unsigned SrcArgIdx, unsigned SizeArgIdx,
-                                 StringRef FunctionName = "");
+                                 unsigned SrcArgIdx, unsigned SizeArgIdx);
 
 private:
   void CheckArrayAccess(const Expr *BaseExpr, const Expr *IndexExpr,
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index d78f71340bd88..be297bf8dc5fe 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1147,8 +1147,7 @@ class FortifiedBufferChecker {
 public:
   FortifiedBufferChecker(Sema &S, FunctionDecl *FD, CallExpr *TheCall)
       : S(S), TheCall(TheCall), FD(FD),
-        DABAttr(FD ? FD->getAttr<DiagnoseAsBuiltinAttr>() : nullptr),
-        UseDABAttr(DABAttr != nullptr) {
+        DABAttr(FD ? FD->getAttr<DiagnoseAsBuiltinAttr>() : nullptr) {
     const TargetInfo &TI = S.getASTContext().getTargetInfo();
     SizeTypeWidth = TI.getTypeWidth(TI.getSizeType());
   }
@@ -1158,7 +1157,7 @@ class FortifiedBufferChecker {
     // argument index to refer to the arguments of the called function. Unless
     // the index is out of bounds, which presumably means it's a variadic
     // function.
-    if (!UseDABAttr)
+    if (DABAttr == nullptr) // Not using DABAttr.
       return Index;
     unsigned DABIndices = DABAttr->argIndices_size();
     unsigned NewIndex = Index < DABIndices
@@ -1232,19 +1231,30 @@ class FortifiedBufferChecker {
   const DiagnoseAsBuiltinAttr *getDABAttr() const { return DABAttr; }
   unsigned getSizeTypeWidth() const { return SizeTypeWidth; }
 
+  /// Return function name after stripping __builtin_ and _chk affixes.
+  std::string GetFunctionName(unsigned BuiltinID, bool IsChkVariant) const {
+    std::string Name = S.getASTContext().BuiltinInfo.getName(BuiltinID);
+    llvm::StringRef Ref = Name;
+    if (IsChkVariant) {
+      Ref = Ref.drop_front(std::strlen("__builtin___"));
+      Ref = Ref.drop_back(std::strlen("_chk"));
+    } else {
+      Ref.consume_front("__builtin_");
+    }
+    return Ref.str();
+  }
+
 private:
   Sema &S;
   CallExpr *TheCall;
   FunctionDecl *FD;
   const DiagnoseAsBuiltinAttr *DABAttr;
-  bool UseDABAttr;
   unsigned SizeTypeWidth;
 };
 } // anonymous namespace
 
 void Sema::checkSourceBufferOverread(FunctionDecl *FD, CallExpr *TheCall,
-                                     unsigned SrcArgIdx, unsigned SizeArgIdx,
-                                     StringRef FunctionName) {
+                                     unsigned SrcArgIdx, unsigned SizeArgIdx) {
   if (TheCall->isValueDependent() || TheCall->isTypeDependent() ||
       isConstantEvaluatedContext())
     return;
@@ -1263,14 +1273,14 @@ void Sema::checkSourceBufferOverread(FunctionDecl *FD, 
CallExpr *TheCall,
   if (llvm::APSInt::compareValues(*CopyLen, *SrcBufSize) <= 0)
     return;
 
-  std::string FuncName;
-  if (FunctionName.empty()) {
-    if (const FunctionDecl *CalleeDecl = TheCall->getDirectCallee())
-      FuncName = CalleeDecl->getName().str();
-    else
-      FuncName = "memory function";
-  } else {
-    FuncName = FunctionName.str();
+  llvm::StringRef FuncName = "memory function";
+  if (const FunctionDecl *CalleeDecl = TheCall->getDirectCallee()) {
+    FuncName = CalleeDecl->getName();
+    // __builtin___memcpy_chk -> memcpy, __builtin_memcpy -> memcpy.
+    // The _chk variants have a different prefix so try that one first.
+    if (!(FuncName.consume_front("__builtin___") &&
+          FuncName.consume_back("_chk")))
+      FuncName.consume_front("__builtin_");
   }
 
   DiagRuntimeBehavior(TheCall->getBeginLoc(), TheCall,
@@ -1437,8 +1447,7 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
     if (BuiltinID == Builtin::BI__builtin___memcpy_chk ||
         BuiltinID == Builtin::BI__builtin___memmove_chk ||
         BuiltinID == Builtin::BI__builtin___mempcpy_chk) {
-      checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/1, /*SizeArgIdx=*/2,
-                                GetFunctionName());
+      checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/1, 
/*SizeArgIdx=*/2);
     }
     break;
   }
@@ -1486,8 +1495,7 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
     // Buffer overread doesn't make sense for memset.
     if (BuiltinID != Builtin::BImemset &&
         BuiltinID != Builtin::BI__builtin_memset) {
-      checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/1, /*SizeArgIdx=*/2,
-                                GetFunctionName());
+      checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/1, 
/*SizeArgIdx=*/2);
     }
     break;
   }
@@ -1495,8 +1503,7 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
   // memchr(buf, val, size)
   case Builtin::BImemchr:
   case Builtin::BI__builtin_memchr: {
-    checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/0, /*SizeArgIdx=*/2,
-                              GetFunctionName());
+    checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/0, /*SizeArgIdx=*/2);
     return;
   }
 
@@ -1506,11 +1513,8 @@ void 
Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD,
   case Builtin::BI__builtin_memcmp:
   case Builtin::BIbcmp:
   case Builtin::BI__builtin_bcmp: {
-    std::string Name = GetFunctionName();
-    checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/0, /*SizeArgIdx=*/2,
-                              Name);
-    checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/1, /*SizeArgIdx=*/2,
-                              Name);
+    checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/0, /*SizeArgIdx=*/2);
+    checkSourceBufferOverread(FD, TheCall, /*SrcArgIdx=*/1, /*SizeArgIdx=*/2);
     return;
   }
 
diff --git a/clang/test/Sema/warn-fortify-source.c 
b/clang/test/Sema/warn-fortify-source.c
index eaa326aa2a778..388dc733a8c19 100644
--- a/clang/test/Sema/warn-fortify-source.c
+++ b/clang/test/Sema/warn-fortify-source.c
@@ -243,12 +243,13 @@ template <int A, int B>
 void call_memcpy_dep() {
   char bufferA[A];
   char bufferB[B];
-  memcpy(bufferA, bufferB, 10); // expected-warning{{'memcpy' will always 
overflow; destination buffer has size 9, but size argument is 10}} \
-                                // expected-warning{{'memcpy' reading 10 bytes 
from a region of size 9}}
+  memcpy(bufferA, bufferB, 10);
 }
 
 void call_call_memcpy() {
   call_memcpy_dep<10, 9>(); // expected-note {{in instantiation of function 
template specialization 'call_memcpy_dep<10, 9>' requested here}}
+                            // expected-warning@-5 {{'memcpy' reading 10 bytes 
from a region of size 9}}
   call_memcpy_dep<9, 10>(); // expected-note {{in instantiation of function 
template specialization 'call_memcpy_dep<9, 10>' requested here}}
+                            // expected-warning@-7 {{'memcpy' will always 
overflow; destination buffer has size 9, but size argument is 10}}
 }
 #endif

>From 4511e6ce14be3bd2fc646e83fd44ead50ef3f65a Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Wed, 25 Feb 2026 23:38:34 +0100
Subject: [PATCH 05/11] Narrow dependency check in checkSourceBufferOverread

---
 clang/lib/Sema/SemaChecking.cpp | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index be297bf8dc5fe..ab47c3cc007a2 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1255,8 +1255,13 @@ class FortifiedBufferChecker {
 
 void Sema::checkSourceBufferOverread(FunctionDecl *FD, CallExpr *TheCall,
                                      unsigned SrcArgIdx, unsigned SizeArgIdx) {
-  if (TheCall->isValueDependent() || TheCall->isTypeDependent() ||
-      isConstantEvaluatedContext())
+  if (isConstantEvaluatedContext())
+    return;
+
+  const Expr *SrcArg = TheCall->getArg(SrcArgIdx);
+  const Expr *SizeArg = TheCall->getArg(SizeArgIdx);
+  if (SrcArg->isValueDependent() || SrcArg->isTypeDependent() ||
+      SizeArg->isValueDependent() || SizeArg->isTypeDependent())
     return;
 
   FortifiedBufferChecker Checker(*this, FD, TheCall);

>From 74f46c3624fb5d98980caa82d04291e840a17922 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Wed, 25 Feb 2026 23:49:07 +0100
Subject: [PATCH 06/11] Add cpp and dependency tests

---
 clang/test/Sema/warn-stringop-overread.c | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/clang/test/Sema/warn-stringop-overread.c 
b/clang/test/Sema/warn-stringop-overread.c
index 746684272729b..27298d663dc5e 100644
--- a/clang/test/Sema/warn-stringop-overread.c
+++ b/clang/test/Sema/warn-stringop-overread.c
@@ -1,5 +1,7 @@
 // RUN: %clang_cc1 %s -verify
 // RUN: %clang_cc1 %s -verify -DUSE_BUILTINS
+// RUN: %clang_cc1 -xc++ %s -verify
+// RUN: %clang_cc1 -xc++ %s -verify -DUSE_BUILTINS
 
 typedef unsigned long size_t;
 
@@ -19,6 +21,8 @@ void *memchr(const void *s, int c, size_t n);
 int memcmp(const void *s1, const void *s2, size_t n);
 #endif
 
+int bcmp(const void *s1, const void *s2, size_t n);
+
 #ifdef __cplusplus
 }
 #endif
@@ -113,8 +117,6 @@ void test_memcpy_src_offset_no_warning(void) {
   memcpy(dst, src + 2, 2); // no warning
 }
 
-int bcmp(const void *s1, const void *s2, size_t n);
-
 void test_bcmp_overread(void) {
   char a[4], b[100];
   bcmp(a, b, 8); // expected-warning {{'bcmp' reading 8 bytes from a region of 
size 4}}
@@ -136,3 +138,16 @@ void test_memmove_chk_overread(void) {
   char src[4];
   __builtin___memmove_chk(dst, src, 8, sizeof(dst)); // expected-warning 
{{'memmove' reading 8 bytes from a region of size 4}}
 }
+
+#ifdef __cplusplus
+template <int N>
+void test_memcpy_dependent_dest() {
+  char dst[N];
+  int src = 0;
+  memcpy(dst, &src, sizeof(src) + 1); // expected-warning {{'memcpy' reading 5 
bytes from a region of size 4}}
+}
+
+void call_test_memcpy_dependent_dest() {
+  test_memcpy_dependent_dest<100>(); // expected-note {{in instantiation}}
+}
+#endif

>From 35418cf9a79cee1f3366a9f69e221fbf313e65b1 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Wed, 25 Feb 2026 23:51:13 +0100
Subject: [PATCH 07/11] Enable warning and catch findings in tests

The macro `#if !defined(VARIANT) || defined(USE_BUILTINS)` in bstring.c
is needed because the call must resolve to a builtin for the diagnostic
to be emitted (which matches -Wfortify-source).
---
 clang/test/AST/ByteCode/builtin-functions.cpp | 21 +++++++-------
 clang/test/Analysis/bstring.c                 | 29 ++++++++++++++++---
 clang/test/Analysis/malloc.c                  |  3 +-
 clang/test/Analysis/pr22954.c                 |  3 +-
 4 files changed, 39 insertions(+), 17 deletions(-)

diff --git a/clang/test/AST/ByteCode/builtin-functions.cpp 
b/clang/test/AST/ByteCode/builtin-functions.cpp
index 91bdffb71f947..ae46b3d876f77 100644
--- a/clang/test/AST/ByteCode/builtin-functions.cpp
+++ b/clang/test/AST/ByteCode/builtin-functions.cpp
@@ -1,17 +1,17 @@
-// RUN: %clang_cc1 -Wno-string-plus-int -Wno-stringop-overread 
-fexperimental-new-constant-interpreter -triple x86_64 %s -verify=expected,both
-// RUN: %clang_cc1 -Wno-string-plus-int -Wno-stringop-overread                 
                        -triple x86_64 %s -verify=ref,both
+// RUN: %clang_cc1 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter -triple x86_64 %s -verify=expected,both
+// RUN: %clang_cc1 -Wno-string-plus-int                                        
 -triple x86_64 %s -verify=ref,both
 //
-// RUN: %clang_cc1 -Wno-string-plus-int -Wno-stringop-overread 
-fexperimental-new-constant-interpreter -triple i686 %s -verify=expected,both
-// RUN: %clang_cc1 -Wno-string-plus-int -Wno-stringop-overread                 
                        -triple i686 %s -verify=ref,both
+// RUN: %clang_cc1 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter -triple i686 %s -verify=expected,both
+// RUN: %clang_cc1 -Wno-string-plus-int                                        
 -triple i686 %s -verify=ref,both
 //
-// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int -Wno-stringop-overread 
-fexperimental-new-constant-interpreter -triple x86_64 %s -verify=expected,both
-// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int -Wno-stringop-overread      
                                   -triple x86_64 %s -verify=ref,both
+// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter -triple x86_64 %s -verify=expected,both
+// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int                             
            -triple x86_64 %s -verify=ref,both
 //
-// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int -Wno-stringop-overread 
-fexperimental-new-constant-interpreter -triple i686 %s -verify=expected,both
-// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int -Wno-stringop-overread      
                                   -triple i686 %s -verify=ref,both
+// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter -triple i686 %s -verify=expected,both
+// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int                             
            -triple i686 %s -verify=ref,both
 //
-// RUN: %clang_cc1 -triple avr -std=c++20 -Wno-string-plus-int 
-Wno-stringop-overread -fexperimental-new-constant-interpreter %s 
-verify=expected,both
-// RUN: %clang_cc1 -triple avr -std=c++20 -Wno-string-plus-int 
-Wno-stringop-overread                                            
-verify=ref,both %s
+// RUN: %clang_cc1 -triple avr -std=c++20 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter %s -verify=expected,both
+// RUN: %clang_cc1 -triple avr -std=c++20 -Wno-string-plus-int                 
                           -verify=ref,both %s
 
 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
 #define LITTLE_END 1
@@ -1621,6 +1621,7 @@ namespace Memcmp {
 
   constexpr int onepasttheend(char a) {
     __builtin_memcmp(&a, &a + 1, 1); // both-note {{read of dereferenced 
one-past-the-end pointer}}
+                                     // ref-warning@-1 {{'memcmp' reading 1 
byte from a region of size 0}}
     return 1;
   }
   static_assert(onepasttheend(10)); // both-error {{not an integral constant 
expression}} \
diff --git a/clang/test/Analysis/bstring.c b/clang/test/Analysis/bstring.c
index d4970489103b0..f45607676482e 100644
--- a/clang/test/Analysis/bstring.c
+++ b/clang/test/Analysis/bstring.c
@@ -1,5 +1,4 @@
 // RUN: %clang_analyze_cc1 -verify %s \
-// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=unix.cstring \
 // RUN:   -analyzer-checker=alpha.unix.cstring \
@@ -8,7 +7,6 @@
 // RUN:   -analyzer-config eagerly-assume=false
 //
 // RUN: %clang_analyze_cc1 -verify %s -DUSE_BUILTINS \
-// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=unix.cstring \
 // RUN:   -analyzer-checker=alpha.unix.cstring \
@@ -17,7 +15,6 @@
 // RUN:   -analyzer-config eagerly-assume=false
 //
 // RUN: %clang_analyze_cc1 -verify %s -DVARIANT \
-// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=unix.cstring \
 // RUN:   -analyzer-checker=alpha.unix.cstring \
@@ -26,7 +23,6 @@
 // RUN:   -analyzer-config eagerly-assume=false
 //
 // RUN: %clang_analyze_cc1 -verify %s -DUSE_BUILTINS -DVARIANT \
-// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=unix.cstring \
 // RUN:   -analyzer-checker=alpha.unix.cstring \
@@ -97,6 +93,9 @@ void memcpy1 (void) {
   char dst[10];
 
   memcpy(dst, src, 5); // expected-warning{{Memory copy function accesses 
out-of-bound array element}}
+#if !defined(VARIANT) || defined(USE_BUILTINS)
+  // expected-warning@-2{{'memcpy' reading 5 bytes from a region of size 4}}
+#endif
 }
 
 void memcpy2 (void) {
@@ -121,6 +120,9 @@ void memcpy4 (void) {
   char dst[10];
 
   memcpy(dst+2, src+2, 3); // expected-warning{{Memory copy function accesses 
out-of-bound array element}}
+#if !defined(VARIANT) || defined(USE_BUILTINS)
+  // expected-warning@-2{{'memcpy' reading 3 bytes from a region of size 2}}
+#endif
 }
 
 void memcpy5(void) {
@@ -223,6 +225,9 @@ void mempcpy1 (void) {
   char dst[10];
 
   mempcpy(dst, src, 5); // expected-warning{{Memory copy function accesses 
out-of-bound array element}}
+#if !defined(VARIANT) || defined(USE_BUILTINS)
+  // expected-warning@-2{{'mempcpy' reading 5 bytes from a region of size 4}}
+#endif
 }
 
 void mempcpy2 (void) {
@@ -247,6 +252,9 @@ void mempcpy4 (void) {
   char dst[10];
 
   mempcpy(dst+2, src+2, 3); // expected-warning{{Memory copy function accesses 
out-of-bound array element}}
+#if !defined(VARIANT) || defined(USE_BUILTINS)
+  // expected-warning@-2{{'mempcpy' reading 3 bytes from a region of size 2}}
+#endif
 }
 
 void mempcpy5(void) {
@@ -388,6 +396,9 @@ void memmove1 (void) {
   char dst[10];
 
   memmove(dst, src, 5); // expected-warning{{out-of-bound}}
+#if !defined(VARIANT) || defined(USE_BUILTINS)
+  // expected-warning@-2{{'memmove' reading 5 bytes from a region of size 4}}
+#endif
 }
 
 void memmove2 (void) {
@@ -430,6 +441,11 @@ void memcmp1 (void) {
   char b[10] = { 0 };
 
   memcmp(a, b, 5); // expected-warning{{out-of-bound}}
+#ifdef VARIANT
+  // expected-warning@-2{{'bcmp' reading 5 bytes from a region of size 4}}
+#else
+  // expected-warning@-4{{'memcmp' reading 5 bytes from a region of size 4}}
+#endif
 }
 
 void memcmp2 (void) {
@@ -437,6 +453,11 @@ void memcmp2 (void) {
   char b[1] = { 0 };
 
   memcmp(a, b, 4); // expected-warning{{out-of-bound}}
+#ifdef VARIANT
+  // expected-warning@-2{{'bcmp' reading 4 bytes from a region of size 1}}
+#else
+  // expected-warning@-4{{'memcmp' reading 4 bytes from a region of size 1}}
+#endif
 }
 
 void memcmp3 (void) {
diff --git a/clang/test/Analysis/malloc.c b/clang/test/Analysis/malloc.c
index 41e512c9a2a2e..4621c3152a8b0 100644
--- a/clang/test/Analysis/malloc.c
+++ b/clang/test/Analysis/malloc.c
@@ -1,6 +1,5 @@
 // RUN: %clang_analyze_cc1 -Wno-strict-prototypes -Wno-error=implicit-int 
-verify %s \
 // RUN:   -Wno-alloc-size \
-// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=alpha.deadcode.UnreachableCode \
 // RUN:   -analyzer-checker=unix \
@@ -880,7 +879,7 @@ void doNotInvalidateWhenPassedToSystemCalls(char *s) {
 // Treat source buffer contents as escaped.
 void escapeSourceContents(char *s) {
   char *p = malloc(12);
-  memcpy(s, &p, 12); // no warning
+  memcpy(s, &p, 12); // expected-warning {{'memcpy' reading 12 bytes from a 
region of size 8}}
 
   void *p1 = malloc(7);
   char *a;
diff --git a/clang/test/Analysis/pr22954.c b/clang/test/Analysis/pr22954.c
index b3910da6c70ab..7e925c26317e1 100644
--- a/clang/test/Analysis/pr22954.c
+++ b/clang/test/Analysis/pr22954.c
@@ -3,7 +3,7 @@
 // At the moment the whole of the destination array content is invalidated.
 // If a.s1 region has a symbolic offset, the whole region of 'a' is 
invalidated.
 // Specific triple set to test structures of size 0.
-// RUN: %clang_analyze_cc1 -triple x86_64-pc-linux-gnu 
-analyzer-checker=core,unix.Malloc,debug.ExprInspection 
-Wno-error=int-conversion -Wno-stringop-overread -verify -analyzer-config 
eagerly-assume=false %s
+// RUN: %clang_analyze_cc1 -triple x86_64-pc-linux-gnu 
-analyzer-checker=core,unix.Malloc,debug.ExprInspection 
-Wno-error=int-conversion -verify -analyzer-config eagerly-assume=false %s
 
 typedef __typeof(sizeof(int)) size_t;
 
@@ -537,6 +537,7 @@ int f262(void) {
   a262.s2 = strdup("hello");
   char input[] = {'a', 'b', 'c', 'd'};
   memcpy(a262.s1, input, -1); // expected-warning{{'memcpy' will always 
overflow; destination buffer has size 16, but size argument is 
18446744073709551615}}
+                              // expected-warning@-1{{'memcpy' reading 
18446744073709551615 bytes from a region of size 4}}
   clang_analyzer_eval(a262.s1[0] == 1); // expected-warning{{UNKNOWN}}\
   expected-warning{{Potential leak of memory pointed to by 'a262.s2'}}
   clang_analyzer_eval(a262.s1[1] == 1); // expected-warning{{UNKNOWN}}

>From 400f5d17da548ad77df249073b4fdc7478a16a47 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Thu, 26 Feb 2026 05:40:35 +0100
Subject: [PATCH 08/11] Revert enabling of warning

---
 clang/test/AST/ByteCode/builtin-functions.cpp | 21 +++++++-------
 clang/test/Analysis/bstring.c                 | 29 +++----------------
 clang/test/Analysis/malloc.c                  |  3 +-
 clang/test/Analysis/pr22954.c                 |  3 +-
 4 files changed, 17 insertions(+), 39 deletions(-)

diff --git a/clang/test/AST/ByteCode/builtin-functions.cpp 
b/clang/test/AST/ByteCode/builtin-functions.cpp
index ae46b3d876f77..91bdffb71f947 100644
--- a/clang/test/AST/ByteCode/builtin-functions.cpp
+++ b/clang/test/AST/ByteCode/builtin-functions.cpp
@@ -1,17 +1,17 @@
-// RUN: %clang_cc1 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter -triple x86_64 %s -verify=expected,both
-// RUN: %clang_cc1 -Wno-string-plus-int                                        
 -triple x86_64 %s -verify=ref,both
+// RUN: %clang_cc1 -Wno-string-plus-int -Wno-stringop-overread 
-fexperimental-new-constant-interpreter -triple x86_64 %s -verify=expected,both
+// RUN: %clang_cc1 -Wno-string-plus-int -Wno-stringop-overread                 
                        -triple x86_64 %s -verify=ref,both
 //
-// RUN: %clang_cc1 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter -triple i686 %s -verify=expected,both
-// RUN: %clang_cc1 -Wno-string-plus-int                                        
 -triple i686 %s -verify=ref,both
+// RUN: %clang_cc1 -Wno-string-plus-int -Wno-stringop-overread 
-fexperimental-new-constant-interpreter -triple i686 %s -verify=expected,both
+// RUN: %clang_cc1 -Wno-string-plus-int -Wno-stringop-overread                 
                        -triple i686 %s -verify=ref,both
 //
-// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter -triple x86_64 %s -verify=expected,both
-// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int                             
            -triple x86_64 %s -verify=ref,both
+// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int -Wno-stringop-overread 
-fexperimental-new-constant-interpreter -triple x86_64 %s -verify=expected,both
+// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int -Wno-stringop-overread      
                                   -triple x86_64 %s -verify=ref,both
 //
-// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter -triple i686 %s -verify=expected,both
-// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int                             
            -triple i686 %s -verify=ref,both
+// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int -Wno-stringop-overread 
-fexperimental-new-constant-interpreter -triple i686 %s -verify=expected,both
+// RUN: %clang_cc1 -std=c++20 -Wno-string-plus-int -Wno-stringop-overread      
                                   -triple i686 %s -verify=ref,both
 //
-// RUN: %clang_cc1 -triple avr -std=c++20 -Wno-string-plus-int 
-fexperimental-new-constant-interpreter %s -verify=expected,both
-// RUN: %clang_cc1 -triple avr -std=c++20 -Wno-string-plus-int                 
                           -verify=ref,both %s
+// RUN: %clang_cc1 -triple avr -std=c++20 -Wno-string-plus-int 
-Wno-stringop-overread -fexperimental-new-constant-interpreter %s 
-verify=expected,both
+// RUN: %clang_cc1 -triple avr -std=c++20 -Wno-string-plus-int 
-Wno-stringop-overread                                            
-verify=ref,both %s
 
 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
 #define LITTLE_END 1
@@ -1621,7 +1621,6 @@ namespace Memcmp {
 
   constexpr int onepasttheend(char a) {
     __builtin_memcmp(&a, &a + 1, 1); // both-note {{read of dereferenced 
one-past-the-end pointer}}
-                                     // ref-warning@-1 {{'memcmp' reading 1 
byte from a region of size 0}}
     return 1;
   }
   static_assert(onepasttheend(10)); // both-error {{not an integral constant 
expression}} \
diff --git a/clang/test/Analysis/bstring.c b/clang/test/Analysis/bstring.c
index f45607676482e..d4970489103b0 100644
--- a/clang/test/Analysis/bstring.c
+++ b/clang/test/Analysis/bstring.c
@@ -1,4 +1,5 @@
 // RUN: %clang_analyze_cc1 -verify %s \
+// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=unix.cstring \
 // RUN:   -analyzer-checker=alpha.unix.cstring \
@@ -7,6 +8,7 @@
 // RUN:   -analyzer-config eagerly-assume=false
 //
 // RUN: %clang_analyze_cc1 -verify %s -DUSE_BUILTINS \
+// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=unix.cstring \
 // RUN:   -analyzer-checker=alpha.unix.cstring \
@@ -15,6 +17,7 @@
 // RUN:   -analyzer-config eagerly-assume=false
 //
 // RUN: %clang_analyze_cc1 -verify %s -DVARIANT \
+// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=unix.cstring \
 // RUN:   -analyzer-checker=alpha.unix.cstring \
@@ -23,6 +26,7 @@
 // RUN:   -analyzer-config eagerly-assume=false
 //
 // RUN: %clang_analyze_cc1 -verify %s -DUSE_BUILTINS -DVARIANT \
+// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=unix.cstring \
 // RUN:   -analyzer-checker=alpha.unix.cstring \
@@ -93,9 +97,6 @@ void memcpy1 (void) {
   char dst[10];
 
   memcpy(dst, src, 5); // expected-warning{{Memory copy function accesses 
out-of-bound array element}}
-#if !defined(VARIANT) || defined(USE_BUILTINS)
-  // expected-warning@-2{{'memcpy' reading 5 bytes from a region of size 4}}
-#endif
 }
 
 void memcpy2 (void) {
@@ -120,9 +121,6 @@ void memcpy4 (void) {
   char dst[10];
 
   memcpy(dst+2, src+2, 3); // expected-warning{{Memory copy function accesses 
out-of-bound array element}}
-#if !defined(VARIANT) || defined(USE_BUILTINS)
-  // expected-warning@-2{{'memcpy' reading 3 bytes from a region of size 2}}
-#endif
 }
 
 void memcpy5(void) {
@@ -225,9 +223,6 @@ void mempcpy1 (void) {
   char dst[10];
 
   mempcpy(dst, src, 5); // expected-warning{{Memory copy function accesses 
out-of-bound array element}}
-#if !defined(VARIANT) || defined(USE_BUILTINS)
-  // expected-warning@-2{{'mempcpy' reading 5 bytes from a region of size 4}}
-#endif
 }
 
 void mempcpy2 (void) {
@@ -252,9 +247,6 @@ void mempcpy4 (void) {
   char dst[10];
 
   mempcpy(dst+2, src+2, 3); // expected-warning{{Memory copy function accesses 
out-of-bound array element}}
-#if !defined(VARIANT) || defined(USE_BUILTINS)
-  // expected-warning@-2{{'mempcpy' reading 3 bytes from a region of size 2}}
-#endif
 }
 
 void mempcpy5(void) {
@@ -396,9 +388,6 @@ void memmove1 (void) {
   char dst[10];
 
   memmove(dst, src, 5); // expected-warning{{out-of-bound}}
-#if !defined(VARIANT) || defined(USE_BUILTINS)
-  // expected-warning@-2{{'memmove' reading 5 bytes from a region of size 4}}
-#endif
 }
 
 void memmove2 (void) {
@@ -441,11 +430,6 @@ void memcmp1 (void) {
   char b[10] = { 0 };
 
   memcmp(a, b, 5); // expected-warning{{out-of-bound}}
-#ifdef VARIANT
-  // expected-warning@-2{{'bcmp' reading 5 bytes from a region of size 4}}
-#else
-  // expected-warning@-4{{'memcmp' reading 5 bytes from a region of size 4}}
-#endif
 }
 
 void memcmp2 (void) {
@@ -453,11 +437,6 @@ void memcmp2 (void) {
   char b[1] = { 0 };
 
   memcmp(a, b, 4); // expected-warning{{out-of-bound}}
-#ifdef VARIANT
-  // expected-warning@-2{{'bcmp' reading 4 bytes from a region of size 1}}
-#else
-  // expected-warning@-4{{'memcmp' reading 4 bytes from a region of size 1}}
-#endif
 }
 
 void memcmp3 (void) {
diff --git a/clang/test/Analysis/malloc.c b/clang/test/Analysis/malloc.c
index 4621c3152a8b0..41e512c9a2a2e 100644
--- a/clang/test/Analysis/malloc.c
+++ b/clang/test/Analysis/malloc.c
@@ -1,5 +1,6 @@
 // RUN: %clang_analyze_cc1 -Wno-strict-prototypes -Wno-error=implicit-int 
-verify %s \
 // RUN:   -Wno-alloc-size \
+// RUN:   -Wno-stringop-overread \
 // RUN:   -analyzer-checker=core \
 // RUN:   -analyzer-checker=alpha.deadcode.UnreachableCode \
 // RUN:   -analyzer-checker=unix \
@@ -879,7 +880,7 @@ void doNotInvalidateWhenPassedToSystemCalls(char *s) {
 // Treat source buffer contents as escaped.
 void escapeSourceContents(char *s) {
   char *p = malloc(12);
-  memcpy(s, &p, 12); // expected-warning {{'memcpy' reading 12 bytes from a 
region of size 8}}
+  memcpy(s, &p, 12); // no warning
 
   void *p1 = malloc(7);
   char *a;
diff --git a/clang/test/Analysis/pr22954.c b/clang/test/Analysis/pr22954.c
index 7e925c26317e1..b3910da6c70ab 100644
--- a/clang/test/Analysis/pr22954.c
+++ b/clang/test/Analysis/pr22954.c
@@ -3,7 +3,7 @@
 // At the moment the whole of the destination array content is invalidated.
 // If a.s1 region has a symbolic offset, the whole region of 'a' is 
invalidated.
 // Specific triple set to test structures of size 0.
-// RUN: %clang_analyze_cc1 -triple x86_64-pc-linux-gnu 
-analyzer-checker=core,unix.Malloc,debug.ExprInspection 
-Wno-error=int-conversion -verify -analyzer-config eagerly-assume=false %s
+// RUN: %clang_analyze_cc1 -triple x86_64-pc-linux-gnu 
-analyzer-checker=core,unix.Malloc,debug.ExprInspection 
-Wno-error=int-conversion -Wno-stringop-overread -verify -analyzer-config 
eagerly-assume=false %s
 
 typedef __typeof(sizeof(int)) size_t;
 
@@ -537,7 +537,6 @@ int f262(void) {
   a262.s2 = strdup("hello");
   char input[] = {'a', 'b', 'c', 'd'};
   memcpy(a262.s1, input, -1); // expected-warning{{'memcpy' will always 
overflow; destination buffer has size 16, but size argument is 
18446744073709551615}}
-                              // expected-warning@-1{{'memcpy' reading 
18446744073709551615 bytes from a region of size 4}}
   clang_analyzer_eval(a262.s1[0] == 1); // expected-warning{{UNKNOWN}}\
   expected-warning{{Potential leak of memory pointed to by 'a262.s2'}}
   clang_analyzer_eval(a262.s1[1] == 1); // expected-warning{{UNKNOWN}}

>From 7c568dbb8e14292c4b7fafa4f8170891eaa7210a Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Thu, 26 Feb 2026 06:36:18 +0100
Subject: [PATCH 09/11] Add uninstantiated template false negative

---
 clang/test/Sema/warn-stringop-overread.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/clang/test/Sema/warn-stringop-overread.c 
b/clang/test/Sema/warn-stringop-overread.c
index 27298d663dc5e..a59573320c618 100644
--- a/clang/test/Sema/warn-stringop-overread.c
+++ b/clang/test/Sema/warn-stringop-overread.c
@@ -150,4 +150,15 @@ void test_memcpy_dependent_dest() {
 void call_test_memcpy_dependent_dest() {
   test_memcpy_dependent_dest<100>(); // expected-note {{in instantiation}}
 }
+
+// FIXME: We should warn here at the template definition since src and size are
+// not dependent, but checkFortifiedBuiltinMemoryFunction exits when any part 
of
+// the call is dependent (and thus uninstantiated).
+template <int N>
+void test_memcpy_dependent_dest_uninstantiated() {
+  char dst[N];
+  int src = 0;
+  memcpy(dst, &src, sizeof(src) + 1); // missing-warning {{'memcpy' reading 5 
bytes from a region of size 4}}
+}
+
 #endif

>From 8970ee88a6690bf057bcf31433bdaba9d69d6111 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Thu, 26 Feb 2026 20:19:37 +0100
Subject: [PATCH 10/11] Assert size arg is unsigned

---
 clang/lib/Sema/SemaChecking.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index ab47c3cc007a2..40135542a38ed 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1179,7 +1179,8 @@ class FortifiedBufferChecker {
     if (!SizeArg->EvaluateAsInt(Result, S.getASTContext()))
       return std::nullopt;
     llvm::APSInt Integer = Result.Val.getInt();
-    Integer.setIsUnsigned(true);
+    assert(Integer.isUnsigned() &&
+           "size arg should be unsigned after implicit conversion to size_t");
     return Integer;
   }
 

>From cb367d5d61566ee7702d7d3216ab92773993ab78 Mon Sep 17 00:00:00 2001
From: John Jepko <[email protected]>
Date: Mon, 2 Mar 2026 16:27:39 +0100
Subject: [PATCH 11/11] Add release note

---
 clang/docs/ReleaseNotes.rst | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 01235ab1df1d9..4cc94f0377bdc 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -271,6 +271,9 @@ Improvements to Clang's diagnostics
 - Clang now emits ``-Wsizeof-pointer-memaccess`` when snprintf/vsnprintf use 
the sizeof 
   the destination buffer(dynamically allocated) in the len parameter(#GH162366)
 
+- Added ``-Wstringop-overread`` to warn when ``memcpy``, ``memmove``, 
``memcmp``,
+  and related builtins read more bytes than the source buffer size (#GH183004).
+
 Improvements to Clang's time-trace
 ----------------------------------
 

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

Reply via email to