https://github.com/jpjepko created https://github.com/llvm/llvm-project/pull/183004
This PR adds a new `-Wstringop-overread` warning that diagnoses calls to memory functions where the specified size exceeds the size of the source buffer, increasing parity with GCC's `-Wstringop-overread`. The warning is emitted when the read size is a compile-time constant that is greater than the size of the source buffer (when known statically). This check applies to the following functions: - `memcpy`, `memmove`, `mempcpy` (and `__builtin_` / `__builtin___*_chk` variants) - `memchr` - `memcmp`, `bcmp` Some of the existing code for `-Wfortify-source` was refactored into a helper class to make its lambdas accessible to other functions. Fixes #83728 Assisted-by: claude-opus-4.6 >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 1/3] 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 2/3] 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 3/3] 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
