https://github.com/jpjepko updated https://github.com/llvm/llvm-project/pull/208012
>From 9dc2f662603c8fa200a524b454c5a5d803485174 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Tue, 7 Jul 2026 00:09:39 +0200 Subject: [PATCH 1/4] Reland "[Clang][Sema] Add -Wstringop-overread warning for source buffer overreads" (#183004) This relands commit 86f2e71cb8d165b59ad31a442b2391e23826133e along with its followup ab1ef459069d1dbf4ee9e5a802537ababe294b25 (#205201), which were reverted in #207840 because they caused an assertion failure in the constant evaluator. The crash is fixed in the following commit. --- clang/docs/ReleaseNotes.md | 166 ++++++++++++ .../clang/Basic/DiagnosticSemaKinds.td | 4 + clang/lib/Sema/SemaChecking.cpp | 245 ++++++++++++------ 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 | 8 +- clang/test/Sema/warn-stringop-overread.c | 175 +++++++++++++ clang/test/SemaCXX/warn-memset-bad-sizeof.cpp | 2 +- .../asan/TestCases/Windows/issue64990.cpp | 2 +- 13 files changed, 534 insertions(+), 100 deletions(-) create mode 100644 clang/test/Sema/warn-stringop-overread.c diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 51dc07318e7ce..93e48944239af 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -123,6 +123,172 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the ### Improvements to Clang's diagnostics +- Fixed bug in `-Wdocumentation` so that it correctly handles explicit + function template instantiations (#64087). + +- Fixed concept template parameters not being recognized in `-Wdocumentation` + when mentioned in tparam comments. (#GH64087) + +- `-Wunused-but-set-variable` now diagnoses file-scope variables with + internal linkage (`static` storage class) that are assigned but never used. + This new coverage is added under the subgroup `-Wunused-but-set-global`, + allowing it to be disabled independently with `-Wno-unused-but-set-global`. + (#GH148361) + +- `-Wunused-template` is now part of `-Wunused` (which is enabled by `-Wall`). + It diagnoses unused function and variable templates with internal linkage, + which in a header is a latent ODR hazard. It can be disabled with + `-Wno-unused-template`. (#GH202945) + +- Added `-Wlifetime-safety` to enable lifetime safety analysis, + a CFG-based intra-procedural analysis that detects use-after-free and related + temporal safety bugs. See the + [RFC](https://discourse.llvm.org/t/rfc-intra-procedural-lifetime-analysis-in-clang/86291) + for more details. By design, this warning is enabled in `-Weverything`. To disable + the analysis, use `-Wno-lifetime-safety` or `-fno-lifetime-safety`. + +- Added `-Wlifetime-safety-suggestions` to enable lifetime annotation suggestions. + This provides suggestions for function parameters that + should be marked `[[clang::lifetimebound]]` based on lifetime analysis. For + example, for the following function: + + ```c++ + int* p(int *in) { return in; } + ``` + + Clang will suggest: + + ```c++ + warning: parameter in intra-TU function should be marked [[clang::lifetimebound]] + int* p(int *in) { return in; } + ^~~~~~~ + [[clang::lifetimebound]] + note: param returned here + int* p(int *in) { return in; } + ^~ + ``` + +- Added `-Wlifetime-safety-noescape` to detect misuse of `[[clang::noescape]]` + annotation where the parameter escapes through return. For example: + + ```c++ + int* p(int *in [[clang::noescape]]) { return in; } + ``` + + Clang will warn: + + ```c++ + warning: parameter is marked [[clang::noescape]] but escapes + int* p(int *in [[clang::noescape]]) { return in; } + ^~~~~~~ + note: returned here + int* p(int *in [[clang::noescape]]) { return in; } + ^~ + ``` + +- Added `-Wlifetime-safety-dangling-field` to detect dangling field references + when stack memory escapes to class fields. This is part of `-Wlifetime-safety` + and detects cases where local variables or parameters are stored in fields but + outlive their scope. For example: + + ```c++ + struct DanglingView { + std::string_view view; + DanglingView(std::string s) : view(s) {} // warning: address of stack memory escapes to a field + }; + ``` + +- Improved `-Wassign-enum` performance by caching enum enumerator values. (#GH176454) + +- Fixed a false negative in `-Warray-bounds` where the warning was suppressed + when accessing a member function on a past-the-end array element. + (#GH179128) + +- Added a missing space to the FixIt for the `implicit-int` group of diagnostics and + made sure that only one such diagnostic and FixIt is emitted per declaration group. (#GH179354) + +- Fixed the Fix-It insertion point for `expected ';' after alias declaration` + when parsing alias declarations involving a token-split `>>` sequence + (for example, `using A = X<int>>;`). (#GH184425) + +- Fixed incorrect `implicitly deleted` diagnostic for explicitly deleted + candidate function. (#GH185693) + +- The `-Wloop-analysis` warning has been extended to catch more cases of + variable modification inside lambda expressions (#GH132038). + +- Clang now emits `-Wsizeof-pointer-memaccess` when snprintf/vsnprintf use the sizeof + the destination buffer(dynamically allocated) in the len parameter(#GH162366) + +- Added `-Wmodule-map-path-outside-directory` (off by default) to warn on + header and umbrella directory paths that use `..` to refer outside the module + directory in module maps found via implicit search + (`-fimplicit-module-maps`). This does not affect module maps specified + explicitly via `-fmodule-map-file=`. + +- Honour `[[maybe_unused]]` attribute on private fields. + `-Wunused-private-field` no longer emits a warning for annotated private + fields. + +- Improved `-Wgnu-zero-variadic-macro-arguments` to suggest using + `__VA_OPT__` if the current language version supports it(#GH188624) + +- Clang now emits an error when implicitly casting a complex type to a built-in vector type. (#GH186805) + +- Added `-Wnonportable-include-path-separator` (off by default) to catch + #include directives that use backslashes as a path separator. The warning + includes a FixIt to change all the backslashes to forward slashes, so that the + code can automatically be made portable to other host platforms that don't + support backslashes. + +- Clang now explains why template deduction fails for explicit template arguments. + +- No longer emitting a `-Wpre-c2y-compat` or extension diagnostic about use + of octal literals with a `0o` prefix, and no longer emitting a + `-Wdeprecated-octal-literals` diagnostic for use of octal literals without + a `0o` prefix, when the literal is expanded from a macro defined in a + system header. (#GH192389) + +- Improved error recovery for missing semicolons after class members. Clang now avoids + skipping subsequent valid declarations when their previous decl is missing semicolon. + +- Removed the body of lambdas from some diagnostic messages. + +- Fixed false positive host-device mismatch errors in discarded `if constexpr` branches for CUDA/HIP; + such calls are now correctly skipped. + +- Clang now errors when a function declaration aliases a variable or vice versa. (#GH195550) + +- Added `-Wattribute-alias` to diagnose type mismatches between an alias and its aliased function. (#GH195550) + +- The diagnostics around `__block` now explain why a variable cannot be marked `__block`. (#GH197213) + +- Extended `-Wnonportable-include-path` to warn about trailing whitespace and dots in `#include` paths. (#GH190610) + +- Clang now emits error when attribute is missing closing `]]` followed by `;;`. (#GH187223) + +- Clang now rejects inline asm constraints and clobbers that contain an + embedded null character, instead of silently truncating them. (#GH173900) + +- Added `-Wstringop-overread` to warn when `memcpy`, `memmove`, `memcmp`, + and related builtins read more bytes than the source buffer size (#GH83728). + +- Diagnostics for the C++11 range-based for statement now report the correct + iterator type in notes for invalid iterator types. + +- `-Wfortify-source` now warns when the constant-evaluated argument to + `umask` has bits set outside `0777`. Those bits are silently discarded + by the kernel, so setting them is almost always a typo (matching the + bionic libc `diagnose_if` check). + +- Improved how Unicode characters are displayed in diagnostic messages. + +- `-Wtautological-pointer-compare` and `-Wpointer-bool-conversion` now + diagnose a reference to a function (e.g. of type `void (&)()`) compared + against or converted to a null pointer, the same as a bare function name. + (#GH46362) + + ### Improvements to Clang's time-trace ### Improvements to Coverage Mapping diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index f14288dd2967d..371ad4535e95f 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -981,6 +981,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<DiagGroup<"stringop-overread">>; + 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/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 2a8395e26d0bd..ef07e8c6133ed 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1147,36 +1147,23 @@ 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) { + 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 // function. - if (!UseDABAttr) + if (!DABAttr) return Index; unsigned DABIndices = DABAttr->argIndices_size(); unsigned NewIndex = Index < DABIndices @@ -1185,25 +1172,25 @@ 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); + assert(Integer.isUnsigned() && + "size arg should be unsigned after implicit conversion to size_t"); 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. @@ -1225,15 +1212,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; @@ -1242,33 +1228,95 @@ 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; - }; + } + + unsigned getSizeTypeWidth() const { return SizeTypeWidth; } + + unsigned getBuiltinID() const { + const FunctionDecl *UseDecl = FD; + if (DABAttr) { + UseDecl = DABAttr->getFunction(); + assert(UseDecl && "Missing FunctionDecl in DiagnoseAsBuiltin attribute!"); + } + return UseDecl->getBuiltinID(/*ConsiderWrappers=*/true); + } + + /// Return function name after stripping __builtin_ and _chk affixes. + std::string getFunctionName() const { + unsigned ID = getBuiltinID(); + if (!ID) { + // Use callee name directly if not a builtin. + const FunctionDecl *Callee = TheCall->getDirectCallee(); + assert(Callee && "expected callee"); + return Callee->getName().str(); + } + std::string Name = S.getASTContext().BuiltinInfo.getName(ID); + StringRef Ref = Name; + // Strip __builtin___*_chk or __builtin_ prefix. + if (!(Ref.consume_front("__builtin___") && Ref.consume_back("_chk"))) + Ref.consume_front("__builtin_"); + assert(!Ref.empty() && "expected non-empty function name"); + return Ref.str(); + } + + /// Check for source buffer overread in memory functions. + void checkSourceOverread(unsigned SrcArgIdx, unsigned SizeArgIdx) { + if (S.isConstantEvaluatedContext()) + return; + + const Expr *SrcArg = TheCall->getArg(SrcArgIdx); + const Expr *SizeArg = TheCall->getArg(SizeArgIdx); + if (SrcArg->isInstantiationDependent() || + SizeArg->isInstantiationDependent()) + return; + + std::optional<llvm::APSInt> CopyLen = + ComputeExplicitObjectSizeArgument(SizeArgIdx); + std::optional<llvm::APSInt> SrcBufSize = ComputeSizeArgument(SrcArgIdx); + + if (!CopyLen || !SrcBufSize) + return; + + // Warn only if copy length exceeds source buffer size. + if (llvm::APSInt::compareValues(*CopyLen, *SrcBufSize) <= 0) + return; + + S.DiagRuntimeBehavior(TheCall->getBeginLoc(), TheCall, + S.PDiag(diag::warn_stringop_overread) + << getFunctionName() << CopyLen->getZExtValue() + << SrcBufSize->getZExtValue()); + } + +private: + Sema &S; + CallExpr *TheCall; + FunctionDecl *FD; + const DiagnoseAsBuiltinAttr *DABAttr; + unsigned SizeTypeWidth; +}; +} // anonymous namespace + +void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, + CallExpr *TheCall) { + if (TheCall->isInstantiationDependent() || isConstantEvaluatedContext()) + return; + + FortifiedBufferChecker Checker(*this, FD, TheCall); + + unsigned BuiltinID = Checker.getBuiltinID(); + if (!BuiltinID) + return; + + unsigned SizeTypeWidth = Checker.getSizeTypeWidth(); std::optional<llvm::APSInt> SourceSize; std::optional<llvm::APSInt> DestinationSize; unsigned DiagID = 0; - bool IsChkVariant = false; - - auto GetFunctionName = [&]() { - std::string FunctionNameStr = - getASTContext().BuiltinInfo.getName(BuiltinID); - llvm::StringRef FunctionName = FunctionNameStr; - // Skim off the details of whichever builtin was called to produce a better - // diagnostic, as it's unlikely that the user wrote the __builtin - // explicitly. - if (IsChkVariant) { - FunctionName = FunctionName.drop_front(std::strlen("__builtin___")); - FunctionName = FunctionName.drop_back(std::strlen("_chk")); - } else { - FunctionName.consume_front("__builtin_"); - } - return FunctionName.str(); - }; switch (BuiltinID) { default: @@ -1280,8 +1328,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; } @@ -1289,9 +1337,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); - IsChkVariant = true; + SourceSize = Checker.ComputeStrLenArgument(1); + DestinationSize = Checker.ComputeExplicitObjectSizeArgument(2); break; } @@ -1317,14 +1364,14 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, unsigned SourceSize) { DiagID = diag::warn_fortify_scanf_overflow; unsigned Index = ArgIndex + DataIndex; - std::string FunctionName = GetFunctionName(); + std::string FunctionName = Checker.getFunctionName(); DiagRuntimeBehavior(TheCall->getArg(Index)->getBeginLoc(), TheCall, PDiag(DiagID) << FunctionName << (Index + 1) << DestSize << SourceSize); }; auto ShiftedComputeSizeArgument = [&](unsigned Index) { - return ComputeSizeArgument(Index + DataIndex); + return Checker.ComputeSizeArgument(Index + DataIndex); }; ScanfDiagnosticFormatHandler H(ShiftedComputeSizeArgument, Diagnose); const char *FormatBytes = FormatStrRef.data(); @@ -1357,10 +1404,9 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, SourceSize = llvm::APSInt::getUnsigned(H.getSizeLowerBound()) .extOrTrunc(SizeTypeWidth); if (BuiltinID == Builtin::BI__builtin___sprintf_chk) { - DestinationSize = ComputeExplicitObjectSizeArgument(2); - IsChkVariant = true; + DestinationSize = Checker.ComputeExplicitObjectSizeArgument(2); } else { - DestinationSize = ComputeSizeArgument(0); + DestinationSize = Checker.ComputeSizeArgument(0); } break; } @@ -1378,19 +1424,24 @@ 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); - IsChkVariant = true; + Checker.ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); + + if (BuiltinID == Builtin::BI__builtin___memcpy_chk || + BuiltinID == Builtin::BI__builtin___memmove_chk || + BuiltinID == Builtin::BI__builtin___mempcpy_chk) { + Checker.checkSourceOverread(/*SrcArgIdx=*/1, /*SizeArgIdx=*/2); + } break; } case Builtin::BI__builtin___snprintf_chk: case Builtin::BI__builtin___vsnprintf_chk: { DiagID = diag::warn_builtin_chk_overflow; - SourceSize = ComputeExplicitObjectSizeArgument(1); - DestinationSize = ComputeExplicitObjectSizeArgument(3); - IsChkVariant = true; + SourceSize = Checker.ComputeExplicitObjectSizeArgument(1); + DestinationSize = Checker.ComputeExplicitObjectSizeArgument(3); break; } @@ -1406,8 +1457,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; } @@ -1422,23 +1474,52 @@ 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); + + // Buffer overread doesn't make sense for memset/bzero. + if (BuiltinID != Builtin::BImemset && + BuiltinID != Builtin::BI__builtin_memset && + BuiltinID != Builtin::BIbzero && + BuiltinID != Builtin::BI__builtin_bzero) { + Checker.checkSourceOverread(/*SrcArgIdx=*/1, /*SizeArgIdx=*/2); + } break; } case Builtin::BIbcopy: case Builtin::BI__builtin_bcopy: { DiagID = diag::warn_fortify_source_overflow; - SourceSize = ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); - DestinationSize = ComputeSizeArgument(1); + SourceSize = + Checker.ComputeExplicitObjectSizeArgument(TheCall->getNumArgs() - 1); + DestinationSize = Checker.ComputeSizeArgument(1); + Checker.checkSourceOverread(/*SrcArgIdx=*/0, /*SizeArgIdx=*/2); break; } + + // memchr(buf, val, size) + case Builtin::BImemchr: + case Builtin::BI__builtin_memchr: { + Checker.checkSourceOverread(/*SrcArgIdx=*/0, /*SizeArgIdx=*/2); + 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: { + Checker.checkSourceOverread(/*SrcArgIdx=*/0, /*SizeArgIdx=*/2); + Checker.checkSourceOverread(/*SrcArgIdx=*/1, /*SizeArgIdx=*/2); + return; + } case Builtin::BIsnprintf: case Builtin::BI__builtin_snprintf: 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; @@ -1462,12 +1543,12 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, FormatSize.toString(FormatSizeStr, /*Radix=*/10); DiagRuntimeBehavior(TheCall->getBeginLoc(), TheCall, PDiag(TruncationDiagID) - << GetFunctionName() << SpecifiedSizeStr - << FormatSizeStr); + << Checker.getFunctionName() + << SpecifiedSizeStr << FormatSizeStr); } } } - 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(); @@ -1479,7 +1560,7 @@ void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, llvm::APSInt::compareValues(*SourceSize, *DestinationSize) <= 0) return; - std::string FunctionName = GetFunctionName(); + std::string FunctionName = Checker.getFunctionName(); SmallString<16> DestinationStr; SmallString<16> SourceStr; diff --git a/clang/test/AST/ByteCode/builtin-functions.cpp b/clang/test/AST/ByteCode/builtin-functions.cpp index d6990d1725072..b14ae91c753cb 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 b337c71eb02c7..7c33b09409725 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-disable-checker=unix.cstring.UninitializedRead \ @@ -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-disable-checker=unix.cstring.UninitializedRead \ @@ -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-disable-checker=unix.cstring.UninitializedRead \ @@ -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-disable-checker=unix.cstring.UninitializedRead \ diff --git a/clang/test/Analysis/malloc.c b/clang/test/Analysis/malloc.c index 4ee09d9022bee..6c3dadfd16021 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 530fa8104bd56..0a6c44f59af9e 100644 --- a/clang/test/Sema/warn-fortify-source.c +++ b/clang/test/Sema/warn-fortify-source.c @@ -100,7 +100,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}} } @@ -271,11 +271,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}} + memcpy(bufferA, bufferB, 10); } 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}} + // 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 diff --git a/clang/test/Sema/warn-stringop-overread.c b/clang/test/Sema/warn-stringop-overread.c new file mode 100644 index 0000000000000..e4a6289a1df2a --- /dev/null +++ b/clang/test/Sema/warn-stringop-overread.c @@ -0,0 +1,175 @@ +// 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 __SIZE_TYPE__ 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 + +int bcmp(const void *s1, const void *s2, size_t n); +void bcopy(const void *src, void *dst, size_t n); + +#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 +} + +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_bcopy_overread(void) { + char src[4], dst[100]; + bcopy(src, dst, 8); // expected-warning {{'bcopy' reading 8 bytes from a region of size 4}} +} + +void test_bcopy_no_warning(void) { + char src[10], dst[10]; + bcopy(src, dst, 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}} +} + +#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}} +} + +// 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 diff --git a/clang/test/SemaCXX/warn-memset-bad-sizeof.cpp b/clang/test/SemaCXX/warn-memset-bad-sizeof.cpp index 6f1cd4dd639ec..405652e3daf5e 100644 --- a/clang/test/SemaCXX/warn-memset-bad-sizeof.cpp +++ b/clang/test/SemaCXX/warn-memset-bad-sizeof.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fsyntax-only -verify -Wno-sizeof-array-argument %s +// RUN: %clang_cc1 -fsyntax-only -verify -Wno-sizeof-array-argument -Wno-stringop-overread %s // extern "C" void *bzero(void *, unsigned); extern "C" void *memset(void *, int, unsigned); diff --git a/compiler-rt/test/asan/TestCases/Windows/issue64990.cpp b/compiler-rt/test/asan/TestCases/Windows/issue64990.cpp index 5222ec6e08191..7fccff0313c62 100644 --- a/compiler-rt/test/asan/TestCases/Windows/issue64990.cpp +++ b/compiler-rt/test/asan/TestCases/Windows/issue64990.cpp @@ -1,5 +1,5 @@ // Repro for the issue #64990: Asan with Windows EH generates __asan_xxx runtime calls without required funclet tokens -// RUN: %clang_cl_asan %Od %if MSVC %{ /Oi %} %s -EHsc %Fe%t +// RUN: %clang_cl_asan %Od %if MSVC %{ /Oi %} %s -EHsc -Wno-stringop-overread %Fe%t // RUN: not %run %t 2>&1 | FileCheck %s // UNSUPPORTED: target={{.*-windows-gnu}} >From 34c24b065c2d8c8b874f27c319a62203629a0575 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Tue, 7 Jul 2026 00:10:07 +0200 Subject: [PATCH 2/4] [Clang][Sema] Fix -Wstringop-overread crash on value-dependent operands -Wstringop-overread introduced in PR #183004 crashes when a memory function is called on &x where x is a static constexpr local inside a template, that is value-dependent but not instantiation-dependent (which happens when taking the address of a templated entity that is not a local-storage variable; computeDependence(UnaryOperator) only sets the Value dependence bit and not the Instantiation bit). The checks for value and type dependency were replaced with checks only to instantiation dependency during the refactor, which caused the assertion and crash. Include in the reland a fix to bring back value and type-dependence checks in checkFortifiedBuiltinMemoryFunction and value-dependence checks for the source and size operands in checkSourceOverread. Add regression test for the crash. --- clang/lib/Sema/SemaChecking.cpp | 8 +++++--- clang/test/Sema/warn-stringop-overread.c | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index ef07e8c6133ed..a83bad52656ca 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1271,8 +1271,9 @@ class FortifiedBufferChecker { const Expr *SrcArg = TheCall->getArg(SrcArgIdx); const Expr *SizeArg = TheCall->getArg(SizeArgIdx); - if (SrcArg->isInstantiationDependent() || - SizeArg->isInstantiationDependent()) + // Need to check both value-dependence and instantiation-dependence. + if (SrcArg->isInstantiationDependent() || SrcArg->isValueDependent() || + SizeArg->isInstantiationDependent() || SizeArg->isValueDependent()) return; std::optional<llvm::APSInt> CopyLen = @@ -1303,7 +1304,8 @@ class FortifiedBufferChecker { void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, CallExpr *TheCall) { - if (TheCall->isInstantiationDependent() || isConstantEvaluatedContext()) + if (TheCall->isInstantiationDependent() || TheCall->isValueDependent() || + TheCall->isTypeDependent() || isConstantEvaluatedContext()) return; FortifiedBufferChecker Checker(*this, FD, TheCall); diff --git a/clang/test/Sema/warn-stringop-overread.c b/clang/test/Sema/warn-stringop-overread.c index e4a6289a1df2a..79d7d690d913c 100644 --- a/clang/test/Sema/warn-stringop-overread.c +++ b/clang/test/Sema/warn-stringop-overread.c @@ -172,4 +172,18 @@ void test_memcpy_dependent_dest_uninstantiated() { memcpy(dst, &src, sizeof(src) + 1); // missing-warning {{'memcpy' reading 5 bytes from a region of size 4}} } +// Taking the address of a static constexpr local inside a template is +// value-dependent but not instantiation-dependent. This regression test +// ensures stringop-overread doesn't crash when trying to constant-evaluate +// such an operand (via an assert in the constant evaluator). +template <typename T> +void test_addr_of_static_constexpr_local() { + static constexpr bool true_value = true; + memcmp(&true_value, &true_value, 1); +} + +void call_test_addr_of_static_constexpr_local() { + test_addr_of_static_constexpr_local<int>(); +} + #endif >From 4cb24a4984fcf31e7d6a56ed26adcd5bfcb07d6e Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Tue, 7 Jul 2026 21:45:09 +0200 Subject: [PATCH 3/4] Add std namespace and fortify tests --- .../Sema/warn-stringop-overread-fortify.c | 29 +++++++++++++++++++ clang/test/Sema/warn-stringop-overread.c | 23 +++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 clang/test/Sema/warn-stringop-overread-fortify.c diff --git a/clang/test/Sema/warn-stringop-overread-fortify.c b/clang/test/Sema/warn-stringop-overread-fortify.c new file mode 100644 index 0000000000000..6a3d2425e0e16 --- /dev/null +++ b/clang/test/Sema/warn-stringop-overread-fortify.c @@ -0,0 +1,29 @@ +// Check that _FORTIFY_SOURCE can interoperate with -Wstringop-overread without +// interfering with one another. We model it here like glibc does so this test +// is target independent. +// +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsyntax-only -D_FORTIFY_SOURCE=1 %s -verify +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsyntax-only -D_FORTIFY_SOURCE=2 %s -verify +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsyntax-only -D_FORTIFY_SOURCE=3 %s -verify +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsyntax-only -D_FORTIFY_SOURCE=2 -fsanitize=address %s -verify +// +// Confirm we actually see _chk wrappers: +// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -D_FORTIFY_SOURCE=2 %s -o - | FileCheck %s + +typedef __SIZE_TYPE__ size_t; + +#if defined(_FORTIFY_SOURCE) && _FORTIFY_SOURCE > 0 +extern __inline __attribute__((__always_inline__, __gnu_inline__)) +void *memcpy(void *dst, const void *src, size_t len) { + return __builtin___memcpy_chk(dst, src, len, __builtin_object_size(dst, 0)); +} +#else +void *memcpy(void *dst, const void *src, size_t c); +#endif + +void test(void) { + char dst[100]; + char src[4]; + // CHECK: call {{.*}}@__memcpy_chk + memcpy(dst, src, 8); // expected-warning {{'memcpy' reading 8 bytes from a region of size 4}} +} diff --git a/clang/test/Sema/warn-stringop-overread.c b/clang/test/Sema/warn-stringop-overread.c index 79d7d690d913c..a0a048412bb91 100644 --- a/clang/test/Sema/warn-stringop-overread.c +++ b/clang/test/Sema/warn-stringop-overread.c @@ -151,6 +151,29 @@ void test_memmove_chk_overread(void) { } #ifdef __cplusplus + +// Mimic how <cstring> brings C library memory functions into the std namespace +// via using-declarations. -Wstringop-overread should fire on them too. Skipped +// under USE_BUILTINS, where these names are function-like macros. +#ifndef USE_BUILTINS +namespace std { +using ::memcpy; +using ::memmove; +} + +void test_std_memcpy_overread() { + char dst[100]; + char src[4]; + std::memcpy(dst, src, 8); // expected-warning {{'memcpy' reading 8 bytes from a region of size 4}} +} + +void test_std_memmove_overread() { + char dst[100]; + char src[4]; + std::memmove(dst, src, 8); // expected-warning {{'memmove' reading 8 bytes from a region of size 4}} +} +#endif + template <int N> void test_memcpy_dependent_dest() { char dst[N]; >From 129757b57554962891fcf892ad7947a05fef5607 Mon Sep 17 00:00:00 2001 From: John Jepko <[email protected]> Date: Tue, 14 Jul 2026 07:16:05 +0200 Subject: [PATCH 4/4] Fix missing instantiation bit bug Revert back to instantiation check after bug fix. --- clang/lib/AST/ComputeDependence.cpp | 2 +- clang/lib/Sema/SemaChecking.cpp | 8 +++----- clang/test/Sema/warn-stringop-overread.c | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/clang/lib/AST/ComputeDependence.cpp b/clang/lib/AST/ComputeDependence.cpp index 95cfd19c0bcc8..a819bb6dec599 100644 --- a/clang/lib/AST/ComputeDependence.cpp +++ b/clang/lib/AST/ComputeDependence.cpp @@ -64,7 +64,7 @@ ExprDependence clang::computeDependence(UnaryOperator *E, if (VD && VD->isTemplated()) { auto *VarD = dyn_cast<VarDecl>(VD); if (!VarD || !VarD->hasLocalStorage()) - Dep |= ExprDependence::Value; + Dep |= ExprDependence::ValueInstantiation; } } } diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index a83bad52656ca..ef07e8c6133ed 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1271,9 +1271,8 @@ class FortifiedBufferChecker { const Expr *SrcArg = TheCall->getArg(SrcArgIdx); const Expr *SizeArg = TheCall->getArg(SizeArgIdx); - // Need to check both value-dependence and instantiation-dependence. - if (SrcArg->isInstantiationDependent() || SrcArg->isValueDependent() || - SizeArg->isInstantiationDependent() || SizeArg->isValueDependent()) + if (SrcArg->isInstantiationDependent() || + SizeArg->isInstantiationDependent()) return; std::optional<llvm::APSInt> CopyLen = @@ -1304,8 +1303,7 @@ class FortifiedBufferChecker { void Sema::checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, CallExpr *TheCall) { - if (TheCall->isInstantiationDependent() || TheCall->isValueDependent() || - TheCall->isTypeDependent() || isConstantEvaluatedContext()) + if (TheCall->isInstantiationDependent() || isConstantEvaluatedContext()) return; FortifiedBufferChecker Checker(*this, FD, TheCall); diff --git a/clang/test/Sema/warn-stringop-overread.c b/clang/test/Sema/warn-stringop-overread.c index a0a048412bb91..f3b70c000581b 100644 --- a/clang/test/Sema/warn-stringop-overread.c +++ b/clang/test/Sema/warn-stringop-overread.c @@ -196,9 +196,8 @@ void test_memcpy_dependent_dest_uninstantiated() { } // Taking the address of a static constexpr local inside a template is -// value-dependent but not instantiation-dependent. This regression test -// ensures stringop-overread doesn't crash when trying to constant-evaluate -// such an operand (via an assert in the constant evaluator). +// value-dependent. This regression test ensures stringop-overread doesn't crash +// when trying to constant-evaluate such an operand. template <typename T> void test_addr_of_static_constexpr_local() { static constexpr bool true_value = true; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
