https://github.com/alejandro-alvarez-sonarsource updated https://github.com/llvm/llvm-project/pull/159357
From 486dd03f291d5a0bdefb372e846670f48086c866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20=C3=81lvarez=20Ayll=C3=B3n?= <alejandro.alva...@sonarsource.com> Date: Mon, 15 Sep 2025 17:51:09 +0200 Subject: [PATCH 1/6] [analyzer] Enhance array bound checking for `ConstantArrayType` - `ArrayBoundChecker` now directly models index accesses on `ConstantArrayType`, enabling more detection of out-of-bounds (OOB) accesses for arrays with known constant sizes. - Improved support for multi-dimensional arrays. By default, positive OOB accesses of "fake" Flexible Array Members (FAM) are silenced, and do not introduce any assumption on the index beyond it not being negative. The option`security.ArrayBound:EnableFakeFlexibleArrayWarn` allows to opt-in for warnings in these cases (without sinking). With this patch we can now warn on OOB access even if we know nothing about the allocation, since the type suffices. Additionally, this also allows to report OOB accesses on subarrays. For instance: ```cpp int array[10][10]; array[0][10]; ``` Before this change, `ArrayBoundChecker` would not report an OOB, since only the byte offset is taken into account: 0 * 10 + 10 = 10, which is less than the allocated 100 bytes. This is, nonetheless, UB per the standard. --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 15 +- .../Checkers/ArrayBoundChecker.cpp | 436 ++++++++++++++++-- .../ArrayBound/assumption-reporting.c | 90 +++- clang/test/Analysis/ArrayBound/brief-tests.c | 191 +++++++- clang/test/Analysis/ArrayBound/cplusplus.cpp | 44 +- .../test/Analysis/ArrayBound/verbose-tests.c | 58 ++- clang/test/Analysis/analyzer-config.c | 1 + 7 files changed, 769 insertions(+), 66 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index a1f5bdfb9edf1..5f60b0ee517bb 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -915,9 +915,18 @@ def decodeValueOfObjCType : Checker<"decodeValueOfObjCType">, let ParentPackage = Security in { - def ArrayBoundChecker : Checker<"ArrayBound">, - HelpText<"Warn about out of bounds access to memory">, - Documentation<HasDocumentation>; + def ArrayBoundChecker + : Checker<"ArrayBound">, + HelpText<"Warn about out of bounds access to memory">, + CheckerOptions<[ + CmdLineOption<Boolean, + "EnableFakeFlexibleArrayWarn", + "Do not silence out-of-bound accesses to arrays of size 1 or 0 that are " + "the last member of a record, or member of an union", + "false", + Released>, + ]>, + Documentation<HasDocumentation>; def FloatLoopCounter : Checker<"FloatLoopCounter">, diff --git a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp index 6ad5acd4e76f2..eed48afbf3933 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp @@ -103,27 +103,6 @@ class StateUpdateReporter { private: std::string getMessage(PathSensitiveBugReport &BR) const; - - /// Return true if information about the value of `Sym` can put constraints - /// on some symbol which is interesting within the bug report `BR`. - /// In particular, this returns true when `Sym` is interesting within `BR`; - /// but it also returns true if `Sym` is an expression that contains integer - /// constants and a single symbolic operand which is interesting (in `BR`). - /// We need to use this instead of plain `BR.isInteresting()` because if we - /// are analyzing code like - /// int array[10]; - /// int f(int arg) { - /// return array[arg] && array[arg + 10]; - /// } - /// then the byte offsets are `arg * 4` and `(arg + 10) * 4`, which are not - /// sub-expressions of each other (but `getSimplifiedOffsets` is smart enough - /// to detect this out of bounds access). - static bool providesInformationAboutInteresting(SymbolRef Sym, - PathSensitiveBugReport &BR); - static bool providesInformationAboutInteresting(SVal SV, - PathSensitiveBugReport &BR) { - return providesInformationAboutInteresting(SV.getAsSymbol(), BR); - } }; struct Messages { @@ -157,12 +136,27 @@ class ArrayBoundChecker : public Checker<check::PostStmt<ArraySubscriptExpr>, BugType BT{this, "Out-of-bound access"}; BugType TaintBT{this, "Out-of-bound access", categories::TaintedData}; + enum class ConstantArrayIndexResult { + Done, //< Could model the index access based on its type + Unknown //< Could not model the array access based on its type + }; + + // `ConstantArrayType`s have a constant size, so use it to check the access. + ConstantArrayIndexResult + performCheckArrayTypeIndex(const ArraySubscriptExpr *E, + CheckerContext &C) const; + void performCheck(const Expr *E, CheckerContext &C) const; void reportOOB(CheckerContext &C, ProgramStateRef ErrorState, Messages Msgs, NonLoc Offset, std::optional<NonLoc> Extent, bool IsTaintBug = false) const; + void warnFlexibleArrayAccess(CheckerContext &C, ProgramStateRef State, + const ArraySubscriptExpr *E, StringRef Name, + NonLoc Index, nonloc::ConcreteInt ArraySize, + QualType ArrayType) const; + static void markPartsInteresting(PathSensitiveBugReport &BR, ProgramStateRef ErrorState, NonLoc Val, bool MarkTaint); @@ -177,8 +171,12 @@ class ArrayBoundChecker : public Checker<check::PostStmt<ArraySubscriptExpr>, static bool isInAddressOf(const Stmt *S, ASTContext &AC); public: + bool EnableFakeFlexibleArrayWarn{false}; + void checkPostStmt(const ArraySubscriptExpr *E, CheckerContext &C) const { - performCheck(E, C); + if (performCheckArrayTypeIndex(E, C) == ConstantArrayIndexResult::Unknown) { + performCheck(E, C); + } } void checkPostStmt(const UnaryOperator *E, CheckerContext &C) const { if (E->getOpcode() == UO_Deref) @@ -478,10 +476,8 @@ static Messages getNonTaintMsgs(const ASTContext &ACtx, std::string(Buf)}; } -static Messages getTaintMsgs(const MemSpaceRegion *Space, - const SubRegion *Region, const char *OffsetName, +static Messages getTaintMsgs(StringRef RegName, const char *OffsetName, bool AlsoMentionUnderflow) { - std::string RegName = getRegionName(Space, Region); return {formatv("Potential out of bound access to {0} with tainted {1}", RegName, OffsetName), formatv("Access of {0} with a tainted {1} that may be {2}too large", @@ -489,6 +485,43 @@ static Messages getTaintMsgs(const MemSpaceRegion *Space, AlsoMentionUnderflow ? "negative or " : "")}; } +/// Return true if information about the value of `Sym` can put constraints +/// on some symbol which is interesting within the bug report `BR`. +/// In particular, this returns true when `Sym` is interesting within `BR`; +/// but it also returns true if `Sym` is an expression that contains integer +/// constants and a single symbolic operand which is interesting (in `BR`). +/// We need to use this instead of plain `BR.isInteresting()` because if we +/// are analyzing code like +/// int array[10]; +/// int f(int arg) { +/// return array[arg] && array[arg + 10]; +/// } +/// then the byte offsets are `arg * 4` and `(arg + 10) * 4`, which are not +/// sub-expressions of each other (but `getSimplifiedOffsets` is smart enough +/// to detect this out of bounds access). +static bool providesInformationAboutInteresting(SymbolRef Sym, + PathSensitiveBugReport &BR) { + if (!Sym) + return false; + for (SymbolRef PartSym : Sym->symbols()) { + // The interestingess mark may appear on any layer as we're stripping off + // the SymIntExpr, UnarySymExpr etc. layers... + if (BR.isInteresting(PartSym)) + return true; + // ...but if both sides of the expression are symbolic, then there is no + // practical algorithm to produce separate constraints for the two + // operands (from the single combined result). + if (isa<SymSymExpr>(PartSym)) + return false; + } + return false; +} + +static bool providesInformationAboutInteresting(SVal SV, + PathSensitiveBugReport &BR) { + return providesInformationAboutInteresting(SV.getAsSymbol(), BR); +} + const NoteTag *StateUpdateReporter::createNoteTag(CheckerContext &C) const { // Don't create a note tag if we didn't assume anything: if (!AssumedNonNegative && !AssumedUpperBound) @@ -555,24 +588,309 @@ std::string StateUpdateReporter::getMessage(PathSensitiveBugReport &BR) const { return std::string(Out.str()); } -bool StateUpdateReporter::providesInformationAboutInteresting( - SymbolRef Sym, PathSensitiveBugReport &BR) { - if (!Sym) - return false; - for (SymbolRef PartSym : Sym->symbols()) { - // The interestingess mark may appear on any layer as we're stripping off - // the SymIntExpr, UnarySymExpr etc. layers... - if (BR.isInteresting(PartSym)) - return true; - // ...but if both sides of the expression are symbolic, then there is no - // practical algorithm to produce separate constraints for the two - // operands (from the single combined result). - if (isa<SymSymExpr>(PartSym)) +// If the base expression of `expr` refers to a `ConstantArrayType`, +// return the element type and the array size. +// Note that "real" Flexible Array Members are `IncompleteArrayType`. +// For them, this function returns `std::nullopt`. +static std::optional<std::pair<QualType, nonloc::ConcreteInt>> +getArrayTypeInfo(SValBuilder &svb, ArraySubscriptExpr const *expr) { + auto const *arrayBaseExpr = expr->getBase()->IgnoreParenImpCasts(); + auto const arrayQualType = arrayBaseExpr->getType().getCanonicalType(); + if (arrayQualType.isNull() || !arrayQualType->isConstantArrayType()) { + return std::nullopt; + } + auto *constArrayType = + dyn_cast<ConstantArrayType>(arrayQualType->getAsArrayTypeUnsafe()); + if (!constArrayType) { + return std::nullopt; + } + return std::make_pair( + constArrayType->getElementType(), + // Note that an array size is technically unsigned, but + // `compareValueToThreshold` (via `getSimplifiedOffsets`) + // will do some arithmetics that could overflow and cause FN. For instance + // ``` + // int array[20]; + // array[unsigned_index + 21]; + // ``` + // `unsigned_index + 21 < 20` is turned into `unsigned_index < 20 - 21`, + // and if 20 is unsigned, that will overflow to the biggest possible array + // which is always trivially true. We obviously do not want that, so we + // need to treat the size as signed. + svb.makeIntVal(llvm::APSInt{constArrayType->getSize(), false})); +} + +static Messages getNegativeIndexMessage(StringRef Name, + nonloc::ConcreteInt ArraySize, + NonLoc Index) { + auto const ArraySizeVal = ArraySize.getValue()->getZExtValue(); + std::string const IndexStr = [&]() -> std::string { + if (auto ConcreteIndex = getConcreteValue(Index); + ConcreteIndex.has_value()) { + return formatv(" {0}", ConcreteIndex); + } + return ""; + }(); + + return {formatv("Out of bound access to {0} at a negative index", Name), + formatv("Access of {0} containing {1} elements at negative index{2}", + Name, ArraySizeVal, IndexStr)}; +} + +static std::string truncateWithEllipsis(StringRef str, size_t maxLength) { + if (str.size() <= maxLength) + return str.str(); + + return (str.substr(0, maxLength - 3) + "...").str(); +} + +static Messages getOOBIndexMessage(StringRef Name, NonLoc Index, + nonloc::ConcreteInt ArraySize, + QualType ElemType, + bool AlsoMentionUnderflow) { + std::optional<int64_t> IndexN = getConcreteValue(Index); + int64_t ExtentN = ArraySize.getValue()->getZExtValue(); + + SmallString<256> Buf; + llvm::raw_svector_ostream Out(Buf); + Out << "Access of " << Name << " at "; + if (AlsoMentionUnderflow) { + Out << "a negative or overflowing index"; + } else if (IndexN.has_value()) { + Out << "index " << *IndexN; + } else { + Out << "an overflowing index"; + } + + const auto ElemTypeStr = truncateWithEllipsis(ElemType.getAsString(), 20); + + Out << ", while it holds only "; + if (ExtentN != 1) + Out << ExtentN << " '" << ElemTypeStr << "' elements"; + else + Out << "a single " << "'" << ElemTypeStr << "' element"; + + return {formatv("Out of bound access to memory {0} {1}", + AlsoMentionUnderflow ? "around" : "after the end of", + Name), + std::string(Buf)}; +} + +// "True" flexible array members do not specify any size (`int elems[];`) +// However, some projects use "fake flexible arrays" (aka "struct hack"), where +// they specify a size of 0 or 1 to work around a compiler limitation. +// "True" flexible array members are `IncompleteArrayType` and will be skipped +// by `performCheckArrayTypeIndex`. We need an heuristic to identify "fake" ones. +static bool isFakeFlexibleArrays(const ArraySubscriptExpr *E) { + auto getFieldDecl = [](ArraySubscriptExpr const *array)-> FieldDecl * { + const Expr *BaseExpr = array->getBase()->IgnoreParenImpCasts(); + if (const MemberExpr *ME = dyn_cast<MemberExpr>(BaseExpr)) { + return dyn_cast<FieldDecl>(ME->getMemberDecl()); + } + return nullptr; + }; + auto const isLastField = [ + ](RecordDecl const *Parent, FieldDecl const *Field) { + const FieldDecl *LastField = nullptr; + for (const FieldDecl *F : Parent->fields()) { + LastField = F; + } + + return (LastField == Field); + }; + // We expect placeholder constant arrays to have size 0 or 1. + auto maybeConstArrayPlaceholder = [](QualType Type) { + const ConstantArrayType *CAT = + dyn_cast<ConstantArrayType>(Type->getAsArrayTypeUnsafe()); + return CAT && CAT->getSize().getZExtValue() <= 1; + }; + + if (auto const *Field = getFieldDecl(E)) { + if (!maybeConstArrayPlaceholder(Field->getType())) return false; + + const RecordDecl *Parent = Field->getParent(); + return Parent && (Parent->isUnion() || isLastField(Parent, Field)); } + return false; } +// Generate a representation of `Expr` suitable for diagnosis. +SmallString<128> ExprReprForDiagnosis(ArraySubscriptExpr const *E, + CheckerContext &C) { + SmallString<128> Buf; + llvm::raw_svector_ostream Out(Buf); + + auto const *Base = E->getBase()->IgnoreParenImpCasts(); + switch (Base->getStmtClass()) { + case Stmt::MemberExprClass: + Out << "the field '"; + Out << dyn_cast<MemberExpr>(Base)->getMemberDecl()->getName().str(); + Out << '\''; + break; + case Stmt::ArraySubscriptExprClass: + Out << "the subarray '"; + Base->printPretty(Out, nullptr, {C.getLangOpts()}); + Out << '\''; + break; + default: + Out << '\''; + Base->printPretty(Out, nullptr, {C.getLangOpts()}); + Out << '\''; + } + + return Buf; +} + +class StateIndexUpdateReporter { + std::string Repr; + QualType ElementType; + NonLoc Index; + nonloc::ConcreteInt ArraySize; + bool AssumedNonNegative = false; + bool AssumedInBounds = false; + + std::string getMessage(PathSensitiveBugReport &BR) const { + SmallString<256> Buf; + if (providesInformationAboutInteresting(Index, BR)) { + llvm::raw_svector_ostream Out{Buf}; + Out << "Assuming index is"; + if (AssumedNonNegative) + Out << " non-negative"; + if (AssumedInBounds) { + if (AssumedNonNegative) + Out << " and"; + Out << " less than " << ArraySize.getValue()->getZExtValue() << ", "; + Out << "the number of '" << ElementType.getAsString() << + "' elements in "; + Out << Repr; + } + } + return std::string{Buf.str()}; + } + +public: + StateIndexUpdateReporter(StringRef Repr, QualType ElementType, NonLoc Index, + nonloc::ConcreteInt ArraySize) + : Repr(Repr), ElementType{ElementType}, Index{Index}, ArraySize{ArraySize} { + } + + void recordNonNegativeAssumption() { + AssumedNonNegative = true; + } + + void recordInBoundsAssumption() { + AssumedInBounds = true; + } + + const NoteTag *createNoteTag(CheckerContext &C) const { + // Don't create a note tag if we didn't assume anything: + if (!AssumedNonNegative && !AssumedInBounds) { + return nullptr; + } + + return C.getNoteTag( + [*this](PathSensitiveBugReport &BR) { return getMessage(BR); }); + } +}; + +// If the array is a `ConstantArrayType`, check the axis size. +// It returns `ConstantArrayIndexResult::Unknown` if it could not reason about +// the array access, deferring to the regular check based on the region. +auto ArrayBoundChecker::performCheckArrayTypeIndex( + const ArraySubscriptExpr *E, + CheckerContext &C) const -> ConstantArrayIndexResult { + auto State = C.getState(); + SValBuilder &SVB = C.getSValBuilder(); + + auto const ArrayInfo = getArrayTypeInfo(SVB, E); + auto const Index = SVB.simplifySVal(State, C.getSVal(E->getIdx())).getAs< + NonLoc>(); + if (!ArrayInfo || !Index) + return ConstantArrayIndexResult::Unknown; + + auto const &[ArrayType, ArraySize] = *ArrayInfo; + + auto const ExprAsStr = ExprReprForDiagnosis(E, C); + bool const IsFakeFAM = isFakeFlexibleArrays(E); + + StateIndexUpdateReporter SUR(ExprAsStr, ArrayType, *Index, ArraySize); + + // Is the index negative? + auto [NegativeIndexState, NonNegativeIndexState] = + compareValueToThreshold(State, *Index, SVB.makeZeroArrayIndex(), SVB); + bool const AlsoMentionUnderflow = (NegativeIndexState != nullptr); + + // Negative is possible + if (NegativeIndexState) { + // But it can't be + if (E->getIdx()->getType()->isUnsignedIntegerOrEnumerationType()) { + // And positive isn't possible + if (!NonNegativeIndexState) { + // The state is broken + return ConstantArrayIndexResult::Done; + } + // As in `performCheck`, we add no assumptions about the index + } else if (!NonNegativeIndexState) { + // Positive is not possible, this is a bug + Messages Msgs = getNegativeIndexMessage(ExprAsStr, ArraySize, *Index); + reportOOB(C, NegativeIndexState, Msgs, *Index, ArraySize); + return ConstantArrayIndexResult::Done; + + } else { + // Both negative and positive are possible, assume positive + SUR.recordNonNegativeAssumption(); + } + } else if (!NonNegativeIndexState) { + // Broken state + return ConstantArrayIndexResult::Done; + } + + // The index is greater than 0, is it within bounds? + auto [WithinUpperBound, OutOfBounds] = + compareValueToThreshold(NonNegativeIndexState, *Index, ArraySize, SVB); + if (!WithinUpperBound && !OutOfBounds) { + // Invalid state + return ConstantArrayIndexResult::Done; + } + + if (!WithinUpperBound) { + if (isIdiomaticPastTheEndPtr(E, OutOfBounds, *Index, ArraySize, C)) { + C.addTransition(OutOfBounds, SUR.createNoteTag(C)); + return ConstantArrayIndexResult::Done; + } + // Silence for fake flexible arrays unless explicitly enabled + if (!IsFakeFAM) { + Messages Msgs = getOOBIndexMessage(ExprAsStr, *Index, ArraySize, ArrayType, + AlsoMentionUnderflow); + reportOOB(C, OutOfBounds, Msgs, *Index, ArraySize); + } else if (EnableFakeFlexibleArrayWarn) { + warnFlexibleArrayAccess(C, OutOfBounds, E, ExprAsStr, *Index, ArraySize, + ArrayType); + } + return ConstantArrayIndexResult::Done; + } + + // The access might be within range, but it may be tainted + if (OutOfBounds && isTainted(OutOfBounds, *Index)) { + Messages Msgs = getTaintMsgs(ExprAsStr, "index", AlsoMentionUnderflow); + reportOOB(C, OutOfBounds, Msgs, *Index, ArraySize, + /*IsTaintBug=*/true); + } + + // When "Flexible Array Members" are involved, assume only non-negative + // even if we want the warning for OOB FAM access. + if (!IsFakeFAM) { + if (WithinUpperBound != NonNegativeIndexState) + SUR.recordInBoundsAssumption(); + C.addTransition(WithinUpperBound, SUR.createNoteTag(C)); + } else + C.addTransition(NonNegativeIndexState, SUR.createNoteTag(C)); + + return ConstantArrayIndexResult::Done; +} + void ArrayBoundChecker::performCheck(const Expr *E, CheckerContext &C) const { const SVal Location = C.getSVal(E); @@ -708,8 +1026,7 @@ void ArrayBoundChecker::performCheck(const Expr *E, CheckerContext &C) const { if (isTainted(State, ASE->getIdx(), C.getLocationContext())) OffsetName = "index"; - Messages Msgs = - getTaintMsgs(Space, Reg, OffsetName, AlsoMentionUnderflow); + Messages Msgs = getTaintMsgs(getRegionName(Space, Reg), OffsetName, AlsoMentionUnderflow); reportOOB(C, ExceedsUpperBound, Msgs, ByteOffset, KnownSize, /*IsTaintBug=*/true); return; @@ -785,6 +1102,40 @@ void ArrayBoundChecker::reportOOB(CheckerContext &C, ProgramStateRef ErrorState, C.emitReport(std::move(BR)); } +void ArrayBoundChecker::warnFlexibleArrayAccess( + CheckerContext &C, ProgramStateRef State, const ArraySubscriptExpr *E, + StringRef Name, NonLoc Index, + nonloc::ConcreteInt ArraySize, QualType ElemType) const { + ExplodedNode *WarnNode = C.generateNonFatalErrorNode(State); + if (WarnNode) { + int64_t ExtentN = ArraySize.getValue()->getZExtValue(); + + assert(ExtentN <= 1 && "Flexible arrays are expected to have size 0 or 1"); + + SmallString<256> Buf; + llvm::raw_svector_ostream Out(Buf); + Out << "Access of " << Name << " containing "; + if (ExtentN != 1) { + Out << ExtentN << " '" << ElemType.getAsString() << "' elements"; + } else { + Out << "a single '" << ElemType.getAsString() << "' element"; + } + Out << " as a possible 'flexible array member'"; + + auto BR = std::make_unique<PathSensitiveBugReport>( + BT, + formatv("Potential out of bound access to {0}, which may be a 'flexible " + "array member'", + Name) + .str(), + Buf, WarnNode); + + markPartsInteresting(*BR, State, Index, false); + markPartsInteresting(*BR, State, ArraySize, false); + C.emitReport(std::move(BR)); + } +} + bool ArrayBoundChecker::isFromCtypeMacro(const Expr *E, ASTContext &ACtx) { SourceLocation Loc = E->getBeginLoc(); if (!Loc.isMacroID()) @@ -837,7 +1188,10 @@ bool ArrayBoundChecker::isIdiomaticPastTheEndPtr(const Expr *E, } void ento::registerArrayBoundChecker(CheckerManager &mgr) { - mgr.registerChecker<ArrayBoundChecker>(); + auto *checker = mgr.registerChecker<ArrayBoundChecker>(); + checker->EnableFakeFlexibleArrayWarn = + mgr.getAnalyzerOptions().getCheckerBooleanOption( + checker->getName(), "EnableFakeFlexibleArrayWarn"); } bool ento::shouldRegisterArrayBoundChecker(const CheckerManager &mgr) { diff --git a/clang/test/Analysis/ArrayBound/assumption-reporting.c b/clang/test/Analysis/ArrayBound/assumption-reporting.c index bffd5d9bc35b5..a6cb86c3f221c 100644 --- a/clang/test/Analysis/ArrayBound/assumption-reporting.c +++ b/clang/test/Analysis/ArrayBound/assumption-reporting.c @@ -69,8 +69,8 @@ int assumingUpper(int arg) { int a = TenElements[arg]; // expected-note@-1 {{Assuming index is less than 10, the number of 'int' elements in 'TenElements'}} int b = TenElements[arg - 10]; - // expected-warning@-1 {{Out of bound access to memory preceding 'TenElements'}} - // expected-note@-2 {{Access of 'TenElements' at a negative index}} + // expected-warning@-1 {{Out of bound access to 'TenElements' at a negative index}} + // expected-note@-2 {{Access of 'TenElements' containing 10 elements at negative index}} return a + b; } @@ -98,8 +98,8 @@ int assumingUpperUnsigned(unsigned arg) { int a = TenElements[arg]; // expected-note@-1 {{Assuming index is less than 10, the number of 'int' elements in 'TenElements'}} int b = TenElements[(int)arg - 10]; - // expected-warning@-1 {{Out of bound access to memory preceding 'TenElements'}} - // expected-note@-2 {{Access of 'TenElements' at a negative index}} + // expected-warning@-1 {{Out of bound access to 'TenElements' at a negative index}} + // expected-note@-2 {{Access of 'TenElements' containing 10 elements at negative index}} return a + b; } @@ -110,8 +110,8 @@ int assumingNothing(unsigned arg) { return 0; int a = TenElements[arg]; // no note here, we already know that 'arg' is in bounds int b = TenElements[(int)arg - 10]; - // expected-warning@-1 {{Out of bound access to memory preceding 'TenElements'}} - // expected-note@-2 {{Access of 'TenElements' at a negative index}} + // expected-warning@-1 {{Out of bound access to 'TenElements' at a negative index}} + // expected-note@-2 {{Access of 'TenElements' containing 10 elements at negative index}} return a + b; } @@ -144,8 +144,8 @@ int assumingConvertedToIntP(struct foo f, int arg) { int b = ((int*)(f.b))[arg]; // expected-note@-1 {{Assuming byte offset is less than 5, the extent of 'f.b'}} int c = TenElements[arg-2]; - // expected-warning@-1 {{Out of bound access to memory preceding 'TenElements'}} - // expected-note@-2 {{Access of 'TenElements' at a negative index}} + // expected-warning@-1 {{Out of bound access to 'TenElements' at a negative index}} + // expected-note@-2 {{Access of 'TenElements' containing 10 elements at negative index}} return a + b + c; } @@ -213,3 +213,77 @@ int triggeredByAnyReport(int arg) { // expected-warning@-1 {{Right operand is negative in right shift}} // expected-note@-2 {{The result of right shift is undefined because the right operand is negative}} } + +extern void clang_analyzer_dump(int); + +int* md_array_assumptions(int x, int y, int z) { + int *mem = (int*)malloc(y); + + int array[80][90][100]; + int value = array[x][y][z]; + // expected-note@-1 {{Assuming index is non-negative and less than 90, the number of 'int[100]' elements in the subarray 'array[x]'}} + + int* ptr = &mem[100]; + // expected-warning@-1 {{Out of bound access to memory after the end of the heap area}} + // expected-note@-2 {{Access of 'int' element in the heap area at index 100}} + if (y) {} + return ptr; +} + +int* md_array_assumptions_2(int x, int y, int z) { + int *mem = (int*)malloc(x); + + int array[80][90][100]; + int value = array[x][y][z]; + // expected-note@-1 {{Assuming index is non-negative and less than 80, the number of 'int[90][100]' elements in 'array'}} + + int* ptr = &mem[100]; + // expected-warning@-1 {{Out of bound access to memory after the end of the heap area}} + // expected-note@-2 {{Access of 'int' element in the heap area at index 100}} + if (x) {} + return ptr; +} + +int* md_array_assumptions_3(int x, int y, int z) { + int *mem = (int*)malloc(z); + + int array[80][90][100]; + int value = array[x][y][z]; + // expected-note@-1 {{Assuming index is non-negative and less than 100, the number of 'int' elements in the subarray 'array[x][y]'}} + + int* ptr = &mem[100]; + // expected-warning@-1 {{Out of bound access to memory after the end of the heap area}} + // expected-note@-2 {{Access of 'int' element in the heap area at index 100}} + if (z) {} + return ptr; +} + + +struct Compound { + int arr[10][20]; +}; + +void compound() { + struct Compound c; + c.arr[3][28] = 1; + // expected-warning@-1 {{Out of bound access to memory after the end of the subarray 'c.arr[3]'}} + // expected-note@-2 {{Access of the subarray 'c.arr[3]' at index 28, while it holds only 20 'int' elements}} +} + +void compound_arg(struct Compound *c) { + c->arr[3][28] = 1; + // expected-warning@-1 {{Out of bound access to memory after the end of the subarray 'c->arr[3]'}} + // expected-note@-2 {{Access of the subarray 'c->arr[3]' at index 28, while it holds only 20 'int' elements}} +} + +int array_arg(int x[10][20]) { + return x[5][22]; + // expected-warning@-1 {{Out of bound access to memory after the end of the subarray 'x[5]'}} + // expected-note@-2 {{Access of the subarray 'x[5]' at index 22, while it holds only 20 'int' elements}} +} + +int ptr_to_array_arg(int (*x)[10][20]) { + return (*x)[5][22]; + // expected-warning@-1 {{Out of bound access to memory after the end of the subarray '(*x)[5]'}} + // expected-note@-2 {{Access of the subarray '(*x)[5]' at index 22, while it holds only 20 'int' elements}} +} diff --git a/clang/test/Analysis/ArrayBound/brief-tests.c b/clang/test/Analysis/ArrayBound/brief-tests.c index f4811efd8d8b6..c8206a3cadace 100644 --- a/clang/test/Analysis/ArrayBound/brief-tests.c +++ b/clang/test/Analysis/ArrayBound/brief-tests.c @@ -1,4 +1,8 @@ // RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,security.ArrayBound,debug.ExprInspection -verify %s +// RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,security.ArrayBound,debug.ExprInspection \ +// RUN: -DWARN_FLEXIBLE_ARRAY -analyzer-config security.ArrayBound:EnableFakeFlexibleArrayWarn=true -verify %s + +#include "../Inputs/system-header-simulator-for-malloc.h" // Miscellaneous tests for `security.ArrayBound` where we only test the // presence or absence of a bug report. If a test doesn't fit in a more @@ -6,6 +10,9 @@ // then it should be placed here. void clang_analyzer_value(int); +void clang_analyzer_eval(int); +void clang_analyzer_warnIfReached(); + // Tests doing an out-of-bounds access after the end of an array using: // - constant integer index @@ -88,7 +95,7 @@ void test1_ptr_arith_ok2(int x) { // - constant integer size for buffer void test2(int x) { int buf[100]; - buf[-1] = 1; // expected-warning{{Out of bound access to memory}} + buf[-1] = 1; // expected-warning{{Out of bound access to 'buf' at a negative index}} } // Tests doing an out-of-bounds access before the start of an array using: @@ -118,7 +125,7 @@ void test2_ptr_arith(int x) { // - constant integer sizes for the array void test2_multi(int x) { int buf[100][100]; - buf[0][-1] = 1; // expected-warning{{Out of bound access to memory}} + buf[0][-1] = 1; // expected-warning{{Out of bound access to the subarray 'buf[0]' at a negative index}} } // Tests doing an out-of-bounds access before the start of a multi-dimensional @@ -127,7 +134,17 @@ void test2_multi(int x) { // - constant integer sizes for the array void test2_multi_b(int x) { int buf[100][100]; - buf[-1][0] = 1; // expected-warning{{Out of bound access to memory}} + buf[-1][0] = 1; // expected-warning{{Out of bound access to 'buf' at a negative index}} +} + +void test2_multi_c() { + int buf[100][100]; + buf[0][103] = 1; // expected-warning{{Out of bound access to memory after the end of the subarray 'buf[0]'}} +} + +void test2_multi_d() { + int buf[80][90][100]; + buf[0][91][0] = 1; // expected-warning {{Out of bound access to memory after the end of the subarray 'buf[0]'}} } void test2_multi_ok(int x) { @@ -138,7 +155,7 @@ void test2_multi_ok(int x) { void test3(int x) { int buf[100]; if (x < 0) - buf[x] = 1; // expected-warning{{Out of bound access to memory}} + buf[x] = 1; // expected-warning{{Out of bound access to 'buf' at a negative index}} } void test4(int x) { @@ -164,7 +181,7 @@ typedef struct { user_t *get_symbolic_user(void); char test_underflow_symbolic_2() { user_t *user = get_symbolic_user(); - return user->name[-1]; // expected-warning{{Out of bound access to memory}} + return user->name[-1]; // expected-warning{{Out of bound access to the field 'name' at a negative index}} } void test_incomplete_struct(void) { @@ -272,3 +289,167 @@ void sizeof_vla_3(int a) { y[5] = 5; // should be {{Out of bounds access}} } } + +void md_array_assumptions(int x, int y, int z) { + int array[80][90][100]; + int value = array[x][y][z]; + + clang_analyzer_eval(x < 80); // expected-warning {{TRUE}} + clang_analyzer_eval(y < 90); // expected-warning {{TRUE}} + clang_analyzer_eval(z < 100); // expected-warning {{TRUE}} + + clang_analyzer_eval(x >= 0); // expected-warning {{TRUE}} + clang_analyzer_eval(y >= 0); // expected-warning {{TRUE}} + clang_analyzer_eval(z >= 0); // expected-warning {{TRUE}} +} + +int unsigned_index_quirk_inner(unsigned char index) { + static const unsigned char array[] = {8, 8, 4, 4, 2, 2, 1}; + + if (index < 6) { + return 0; + } + + // Unsurprising + clang_analyzer_eval(index >= 6); // expected-warning {{TRUE}} + + index++; + + // Could overflow since 255 is a possible value, kind of surprising + clang_analyzer_eval(index > 6); // expected-warning {{TRUE}} expected-warning {{FALSE}} + + if (index >= 7) { + return 0; + } + + // Because of the if above, 7 is not possible. But because of the overflow, 0 + // is. + clang_analyzer_eval(index == 0); // expected-warning {{TRUE}} + + // Regression test: if we don't call `simplifySVal` so 255+1 is folded into 0, + // we get a false positive "out of bounds". + return array[index]; // no warning +} + +struct WithTrailingArray { + int nargs; + int args[1]; +}; + +int use_trailing(struct WithTrailingArray *p) { +#ifdef WARN_FLEXIBLE_ARRAY + // expected-warning@+2 {{Potential out of bound access to the field 'args', which may be a 'flexible array member'}} +#endif + int x = p->args[3]; + // No sink + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + return x; +} + +struct NotReallyTrailing { + int array[1]; + int more_data; +}; + +int use_non_trailing(struct NotReallyTrailing *p) { + int x = p->array[3]; // expected-warning {{Out of bound access to memory after the end of the field 'array'}} + clang_analyzer_warnIfReached(); // sink, no warning + return x; +} + +struct TrailingNoException { + int n; + int some[2]; +}; + +int use_trailing_no_exception(struct TrailingNoException *p) { + int x = p->some[10]; // expected-warning {{Out of bound access to memory after the end of the field 'some'}} + clang_analyzer_warnIfReached(); // sink, no warning + return x; +} + +int regular_one_array() { + int array[1]; + int x = array[2]; // expected-warning {{Out of bound access to memory after the end of 'array'}} + clang_analyzer_warnIfReached(); // sink, no warning + return x; +} + +struct WithTrailing0 { + int nargs; + int args[0]; +}; + +int use_trailing0(struct WithTrailing0 *p) { +#ifdef WARN_FLEXIBLE_ARRAY + // expected-warning@+2 {{Potential out of bound access to the field 'args', which may be a 'flexible array member'}} +#endif + int x = p->args[10]; // no warning + // No sink + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + return x; +} + +struct RealFlexibleArray { + int nargs; + int args[]; +}; + +// Trailing array, allocation unknown +int use_real_trailing(struct RealFlexibleArray *p) { + int x = p->args[3]; // no warning + // No sink + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + return x; +} + +int use_allocated_fam() { + struct RealFlexibleArray *p = malloc(sizeof(struct RealFlexibleArray) + 10 * sizeof(int)); + // This is a false negative. + // `ArrayBoundCheckers` queries for the extent of the memory region + // `Element{SymRegion{conj_$2{void *, LC1, S1173, #1}},0 S64b,struct RealFlexibleArray}.args`, + // which is unknown. Either `ArrayBoundCheckers`, or `DynamicExtent.cpp` should + // learn how to handle this pattern. + int x = p->args[30]; + free(p); + return x; +} + +// Pattern used in GCC +union FlexibleArrayUnion { + int args[1]; + struct {int x, y, z;}; +}; + +int use_union(union FlexibleArrayUnion *p) { +#ifdef WARN_FLEXIBLE_ARRAY + // expected-warning@+2 {{Potential out of bound access to the field 'args', which may be a 'flexible array member'}} +#endif + int x = p->args[2]; + // No sink + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + return x; +} + +void custom_unreachable(); + +int get_index(char b) { + switch (b) { + case 'a': + return 0; + default: + custom_unreachable(); + } + // expected-warning@+1 {{non-void function does not return a value in all control paths}} +} + +int access_by_undefined(char b) { + char array[10] = {0}; + int i = get_index(b); + // CPP-7012, even though there is a path that does return a value, CSA keeps going + clang_analyzer_value(i); // expected-warning {{[-2147483648, 2147483647]}} expected-warning {{32s:0}} + int x = array[i]; + // `i` could be [0, 9], but `get_index` should have pruned [1, 9] out of the possible scenarios + clang_analyzer_value(i); // expected-warning {{[0, 9]}} expected-warning {{32s:0}} + return x; +} diff --git a/clang/test/Analysis/ArrayBound/cplusplus.cpp b/clang/test/Analysis/ArrayBound/cplusplus.cpp index 680fbf4817c30..8b9cb450a9e2a 100644 --- a/clang/test/Analysis/ArrayBound/cplusplus.cpp +++ b/clang/test/Analysis/ArrayBound/cplusplus.cpp @@ -1,4 +1,6 @@ -// RUN: %clang_analyze_cc1 -std=c++11 -Wno-array-bounds -analyzer-checker=unix,core,security.ArrayBound -verify %s +// RUN: %clang_analyze_cc1 -std=c++11 -Wno-array-bounds -analyzer-checker=unix,core,security.ArrayBound,debug.ExprInspection -verify %s + +void clang_analyzer_value(int); // Test the interactions of `security.ArrayBound` with C++ features. @@ -96,7 +98,7 @@ void test2_ptr_arith(int x) { // of a multi-dimensional array void test2_multi(int x) { auto buf = new int[100][100]; - buf[0][-1] = 1; // expected-warning{{Out of bound access to memory}} + buf[0][-1] = 1; // expected-warning{{Out of bound access to the subarray 'buf[0]' at a negative index}} } // Tests under-indexing @@ -117,9 +119,20 @@ void test2_multi_c(int x) { // of a multi-dimensional array void test2_multi_2(int x) { auto buf = new int[100][100]; - buf[99][100] = 1; // expected-warning{{Out of bound access to memory}} + buf[99][100] = 1; // expected-warning{{Out of bound access to memory after the end of the subarray 'buf[99]'}} +} + +void test2_multi_3() { + auto buf = new int[100][100]; + buf[0][100] = 1; // expected-warning{{Out of bound access to memory after the end of the subarray 'buf[0]'}} } +void test2_multi_4() { + auto buf = new int[100][100][100]; + buf[0][101][0] = 1; // expected-warning{{Out of bound access to memory after the end of the subarray 'buf[0]'}} +} + + // Tests normal access of // a multi-dimensional array void test2_multi_ok(int x) { @@ -179,3 +192,28 @@ int test_reference_that_might_be_after_the_end(int idx) { return -1; return ref; } + +int test_type_alias() { + using Arr1 = int[10]; + using Arr2 = Arr1[20]; + Arr2 arr; + return arr[0][30]; // expected-warning {{Out of bound access to memory after the end of the subarray 'arr[0]'}} +} + +int* test_type_alias_2() { + using Arr1 = int[10]; + using Arr2 = Arr1[20]; + Arr2* arr = new Arr2[]{}; + return (*arr)[30]; // expected-warning {{Out of bound access to memory after the end of the heap area}} +} + +// Similar to `out-of-bounds.c` +// See Github ticket #39492. +int test_cast_to_unsigned(signed char x) { + int *table = new int[256]; + unsigned char y = x; + if (x >= 0) + return x; + clang_analyzer_value(y); // expected-warning {{8s:{ [-128, -1] } }} + return table[y]; // no-warning +} diff --git a/clang/test/Analysis/ArrayBound/verbose-tests.c b/clang/test/Analysis/ArrayBound/verbose-tests.c index e3416886d13e5..f23a0e1cde941 100644 --- a/clang/test/Analysis/ArrayBound/verbose-tests.c +++ b/clang/test/Analysis/ArrayBound/verbose-tests.c @@ -10,8 +10,8 @@ int TenElements[10]; void arrayUnderflow(void) { TenElements[-3] = 5; - // expected-warning@-1 {{Out of bound access to memory preceding 'TenElements'}} - // expected-note@-2 {{Access of 'int' element in 'TenElements' at negative index -3}} + // expected-warning@-1 {{Out of bound access to 'TenElements' at a negative index}} + // expected-note@-2 {{Access of 'TenElements' containing 10 elements at negative index -3}} } int underflowWithDeref(void) { @@ -69,8 +69,8 @@ void gh86959(void) { // expected-note@+1 {{Entering loop body}} while (rng()) TenElements[getIndex()] = 10; - // expected-warning@-1 {{Out of bound access to memory preceding 'TenElements'}} - // expected-note@-2 {{Access of 'int' element in 'TenElements' at negative index -172}} + // expected-warning@-1 {{Out of bound access to 'TenElements' at a negative index}} + // expected-note@-2 {{Access of 'TenElements' containing 10 elements at negative index -172}} } int scanf(const char *fmt, ...); @@ -240,8 +240,8 @@ struct vec { double arrayInStruct(void) { return v.elems[64]; - // expected-warning@-1 {{Out of bound access to memory after the end of 'v.elems'}} - // expected-note@-2 {{Access of 'v.elems' at index 64, while it holds only 64 'double' elements}} + // expected-warning@-1 {{Out of bound access to memory after the end of the field 'elems'}} + // expected-note@-2 {{Access of the field 'elems' at index 64, while it holds only 64 'double' elements}} } double arrayInStructPtr(struct vec *pv) { @@ -432,3 +432,49 @@ int *nothingIsCertain(int x, int y) { return mem; } + +// Should raise when the index for a given sub-array is out-of-bounds +extern int actionBufferList[200][100]; + +void recvToSubArrayIndex() { + int index = 0; + scanf("%d", &index); + // expected-note@-1 {{Taint originated here}} + // expected-note@-2 {{Taint propagated to the 2nd argument}} + + // expected-note@+2 {{Assuming 'index' is <= 100}} + // expected-note@+1 {{Taking false branch}} + if (index > 100) { + return; + } + + actionBufferList[0][index] = '\0'; + // expected-warning@-1 {{Potential out of bound access to the subarray 'actionBufferList[0]' with tainted index}} + // expected-note@-2 {{Access of the subarray 'actionBufferList[0]' with a tainted index that may be negative or too large}} +} + + +#define LONGSTRUCT(x) struct VERYLONGPREFIXIFWEPRINTEVERYTHINGISUNREADABLE ## x + +LONGSTRUCT(Bar) { + int x; +}; + +int test_case_long_elem_name() { + LONGSTRUCT(Bar) table[10]; + return table[55].x; + // expected-warning@-1 {{Out of bound access to memory after the end of 'table'}} + // expected-note@-2 {{Access of 'table' at index 55, while it holds only 10 'struct VERYLONGPR...' elements}} +} + +struct What { + int x[0]; + int y; +}; + +int who() { + struct What who; + return who.x[1]; + // expected-warning@-1 {{Out of bound access to memory after the end of the field 'x'}} + // expected-note@-2 {{Access of the field 'x' at index 1, while it holds only 0 'int' elements}} +} diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index 7936273415ad4..d4067ddf3bd6a 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -120,6 +120,7 @@ // CHECK-NEXT: region-store-small-array-limit = 5 // CHECK-NEXT: region-store-small-struct-limit = 2 // CHECK-NEXT: report-in-main-source-file = false +// CHECK-NEXT: security.ArrayBound:EnableFakeFlexibleArrayWarn = false // CHECK-NEXT: security.cert.env.InvalidPtr:InvalidatingGetEnv = false // CHECK-NEXT: serialize-stats = false // CHECK-NEXT: silence-checkers = "" From ce70364dd08d1a1f98fea5b1319aa1a462759b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20=C3=81lvarez=20Ayll=C3=B3n?= <alejandro.alva...@sonarsource.com> Date: Wed, 17 Sep 2025 16:10:10 +0200 Subject: [PATCH 2/6] Formatting --- .../Checkers/ArrayBoundChecker.cpp | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp index eed48afbf3933..72b1b61924a48 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp @@ -137,7 +137,7 @@ class ArrayBoundChecker : public Checker<check::PostStmt<ArraySubscriptExpr>, BugType TaintBT{this, "Out-of-bound access", categories::TaintedData}; enum class ConstantArrayIndexResult { - Done, //< Could model the index access based on its type + Done, //< Could model the index access based on its type Unknown //< Could not model the array access based on its type }; @@ -626,7 +626,7 @@ static Messages getNegativeIndexMessage(StringRef Name, auto const ArraySizeVal = ArraySize.getValue()->getZExtValue(); std::string const IndexStr = [&]() -> std::string { if (auto ConcreteIndex = getConcreteValue(Index); - ConcreteIndex.has_value()) { + ConcreteIndex.has_value()) { return formatv(" {0}", ConcreteIndex); } return ""; @@ -671,8 +671,7 @@ static Messages getOOBIndexMessage(StringRef Name, NonLoc Index, Out << "a single " << "'" << ElemTypeStr << "' element"; return {formatv("Out of bound access to memory {0} {1}", - AlsoMentionUnderflow ? "around" : "after the end of", - Name), + AlsoMentionUnderflow ? "around" : "after the end of", Name), std::string(Buf)}; } @@ -680,17 +679,18 @@ static Messages getOOBIndexMessage(StringRef Name, NonLoc Index, // However, some projects use "fake flexible arrays" (aka "struct hack"), where // they specify a size of 0 or 1 to work around a compiler limitation. // "True" flexible array members are `IncompleteArrayType` and will be skipped -// by `performCheckArrayTypeIndex`. We need an heuristic to identify "fake" ones. +// by `performCheckArrayTypeIndex`. We need an heuristic to identify "fake" +// ones. static bool isFakeFlexibleArrays(const ArraySubscriptExpr *E) { - auto getFieldDecl = [](ArraySubscriptExpr const *array)-> FieldDecl * { + auto getFieldDecl = [](ArraySubscriptExpr const *array) -> FieldDecl * { const Expr *BaseExpr = array->getBase()->IgnoreParenImpCasts(); if (const MemberExpr *ME = dyn_cast<MemberExpr>(BaseExpr)) { return dyn_cast<FieldDecl>(ME->getMemberDecl()); } return nullptr; }; - auto const isLastField = [ - ](RecordDecl const *Parent, FieldDecl const *Field) { + auto const isLastField = [](RecordDecl const *Parent, + FieldDecl const *Field) { const FieldDecl *LastField = nullptr; for (const FieldDecl *F : Parent->fields()) { LastField = F; @@ -762,8 +762,8 @@ class StateIndexUpdateReporter { if (AssumedNonNegative) Out << " and"; Out << " less than " << ArraySize.getValue()->getZExtValue() << ", "; - Out << "the number of '" << ElementType.getAsString() << - "' elements in "; + Out << "the number of '" << ElementType.getAsString() + << "' elements in "; Out << Repr; } } @@ -773,16 +773,12 @@ class StateIndexUpdateReporter { public: StateIndexUpdateReporter(StringRef Repr, QualType ElementType, NonLoc Index, nonloc::ConcreteInt ArraySize) - : Repr(Repr), ElementType{ElementType}, Index{Index}, ArraySize{ArraySize} { - } + : Repr(Repr), ElementType{ElementType}, Index{Index}, + ArraySize{ArraySize} {} - void recordNonNegativeAssumption() { - AssumedNonNegative = true; - } + void recordNonNegativeAssumption() { AssumedNonNegative = true; } - void recordInBoundsAssumption() { - AssumedInBounds = true; - } + void recordInBoundsAssumption() { AssumedInBounds = true; } const NoteTag *createNoteTag(CheckerContext &C) const { // Don't create a note tag if we didn't assume anything: @@ -798,15 +794,15 @@ class StateIndexUpdateReporter { // If the array is a `ConstantArrayType`, check the axis size. // It returns `ConstantArrayIndexResult::Unknown` if it could not reason about // the array access, deferring to the regular check based on the region. -auto ArrayBoundChecker::performCheckArrayTypeIndex( - const ArraySubscriptExpr *E, - CheckerContext &C) const -> ConstantArrayIndexResult { +auto ArrayBoundChecker::performCheckArrayTypeIndex(const ArraySubscriptExpr *E, + CheckerContext &C) const + -> ConstantArrayIndexResult { auto State = C.getState(); SValBuilder &SVB = C.getSValBuilder(); auto const ArrayInfo = getArrayTypeInfo(SVB, E); - auto const Index = SVB.simplifySVal(State, C.getSVal(E->getIdx())).getAs< - NonLoc>(); + auto const Index = + SVB.simplifySVal(State, C.getSVal(E->getIdx())).getAs<NonLoc>(); if (!ArrayInfo || !Index) return ConstantArrayIndexResult::Unknown; @@ -862,8 +858,8 @@ auto ArrayBoundChecker::performCheckArrayTypeIndex( } // Silence for fake flexible arrays unless explicitly enabled if (!IsFakeFAM) { - Messages Msgs = getOOBIndexMessage(ExprAsStr, *Index, ArraySize, ArrayType, - AlsoMentionUnderflow); + Messages Msgs = getOOBIndexMessage(ExprAsStr, *Index, ArraySize, + ArrayType, AlsoMentionUnderflow); reportOOB(C, OutOfBounds, Msgs, *Index, ArraySize); } else if (EnableFakeFlexibleArrayWarn) { warnFlexibleArrayAccess(C, OutOfBounds, E, ExprAsStr, *Index, ArraySize, @@ -1026,7 +1022,8 @@ void ArrayBoundChecker::performCheck(const Expr *E, CheckerContext &C) const { if (isTainted(State, ASE->getIdx(), C.getLocationContext())) OffsetName = "index"; - Messages Msgs = getTaintMsgs(getRegionName(Space, Reg), OffsetName, AlsoMentionUnderflow); + Messages Msgs = getTaintMsgs(getRegionName(Space, Reg), OffsetName, + AlsoMentionUnderflow); reportOOB(C, ExceedsUpperBound, Msgs, ByteOffset, KnownSize, /*IsTaintBug=*/true); return; @@ -1102,10 +1099,12 @@ void ArrayBoundChecker::reportOOB(CheckerContext &C, ProgramStateRef ErrorState, C.emitReport(std::move(BR)); } -void ArrayBoundChecker::warnFlexibleArrayAccess( - CheckerContext &C, ProgramStateRef State, const ArraySubscriptExpr *E, - StringRef Name, NonLoc Index, - nonloc::ConcreteInt ArraySize, QualType ElemType) const { +void ArrayBoundChecker::warnFlexibleArrayAccess(CheckerContext &C, + ProgramStateRef State, + const ArraySubscriptExpr *E, + StringRef Name, NonLoc Index, + nonloc::ConcreteInt ArraySize, + QualType ElemType) const { ExplodedNode *WarnNode = C.generateNonFatalErrorNode(State); if (WarnNode) { int64_t ExtentN = ArraySize.getValue()->getZExtValue(); @@ -1124,9 +1123,10 @@ void ArrayBoundChecker::warnFlexibleArrayAccess( auto BR = std::make_unique<PathSensitiveBugReport>( BT, - formatv("Potential out of bound access to {0}, which may be a 'flexible " - "array member'", - Name) + formatv( + "Potential out of bound access to {0}, which may be a 'flexible " + "array member'", + Name) .str(), Buf, WarnNode); From 7f40e72cc09fec9254a1c25f4a07860fd62cf211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20=C3=81lvarez=20Ayll=C3=B3n?= <alejandro.alva...@sonarsource.com> Date: Thu, 18 Sep 2025 15:38:58 +0200 Subject: [PATCH 3/6] Style --- .../Checkers/ArrayBoundChecker.cpp | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp index 72b1b61924a48..d0d048b2842be 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp @@ -593,19 +593,19 @@ std::string StateUpdateReporter::getMessage(PathSensitiveBugReport &BR) const { // Note that "real" Flexible Array Members are `IncompleteArrayType`. // For them, this function returns `std::nullopt`. static std::optional<std::pair<QualType, nonloc::ConcreteInt>> -getArrayTypeInfo(SValBuilder &svb, ArraySubscriptExpr const *expr) { - auto const *arrayBaseExpr = expr->getBase()->IgnoreParenImpCasts(); - auto const arrayQualType = arrayBaseExpr->getType().getCanonicalType(); - if (arrayQualType.isNull() || !arrayQualType->isConstantArrayType()) { +getArrayTypeInfo(SValBuilder &SVB, ArraySubscriptExpr const *E) { + Expr const *ArrayBaseExpr = E->getBase()->IgnoreParenImpCasts(); + QualType const ArrayQualType = ArrayBaseExpr->getType().getCanonicalType(); + if (ArrayQualType.isNull() || !ArrayQualType->isConstantArrayType()) return std::nullopt; - } - auto *constArrayType = - dyn_cast<ConstantArrayType>(arrayQualType->getAsArrayTypeUnsafe()); - if (!constArrayType) { + + auto *ConstArrayType = + dyn_cast<ConstantArrayType>(ArrayQualType->getAsArrayTypeUnsafe()); + if (!ConstArrayType) return std::nullopt; - } + return std::make_pair( - constArrayType->getElementType(), + ConstArrayType->getElementType(), // Note that an array size is technically unsigned, but // `compareValueToThreshold` (via `getSimplifiedOffsets`) // will do some arithmetics that could overflow and cause FN. For instance @@ -617,17 +617,16 @@ getArrayTypeInfo(SValBuilder &svb, ArraySubscriptExpr const *expr) { // and if 20 is unsigned, that will overflow to the biggest possible array // which is always trivially true. We obviously do not want that, so we // need to treat the size as signed. - svb.makeIntVal(llvm::APSInt{constArrayType->getSize(), false})); + SVB.makeIntVal(llvm::APSInt{ConstArrayType->getSize(), false})); } static Messages getNegativeIndexMessage(StringRef Name, nonloc::ConcreteInt ArraySize, NonLoc Index) { - auto const ArraySizeVal = ArraySize.getValue()->getZExtValue(); + uint64_t const ArraySizeVal = ArraySize.getValue()->getZExtValue(); std::string const IndexStr = [&]() -> std::string { - if (auto ConcreteIndex = getConcreteValue(Index); - ConcreteIndex.has_value()) { - return formatv(" {0}", ConcreteIndex); + if (std::optional<int64_t> ConcreteIndex = getConcreteValue(Index)) { + return formatv(" {0}", *ConcreteIndex); } return ""; }(); @@ -637,11 +636,11 @@ static Messages getNegativeIndexMessage(StringRef Name, Name, ArraySizeVal, IndexStr)}; } -static std::string truncateWithEllipsis(StringRef str, size_t maxLength) { - if (str.size() <= maxLength) - return str.str(); +static std::string truncateWithEllipsis(StringRef Str, size_t MaxLength) { + if (Str.size() <= MaxLength) + return Str.str(); - return (str.substr(0, maxLength - 3) + "...").str(); + return (Str.substr(0, MaxLength - 3) + "...").str(); } static Messages getOOBIndexMessage(StringRef Name, NonLoc Index, @@ -662,7 +661,8 @@ static Messages getOOBIndexMessage(StringRef Name, NonLoc Index, Out << "an overflowing index"; } - const auto ElemTypeStr = truncateWithEllipsis(ElemType.getAsString(), 20); + std::string const ElemTypeStr = + truncateWithEllipsis(ElemType.getAsString(), 20); Out << ", while it holds only "; if (ExtentN != 1) @@ -705,7 +705,7 @@ static bool isFakeFlexibleArrays(const ArraySubscriptExpr *E) { return CAT && CAT->getSize().getZExtValue() <= 1; }; - if (auto const *Field = getFieldDecl(E)) { + if (FieldDecl const *Field = getFieldDecl(E)) { if (!maybeConstArrayPlaceholder(Field->getType())) return false; @@ -717,8 +717,8 @@ static bool isFakeFlexibleArrays(const ArraySubscriptExpr *E) { } // Generate a representation of `Expr` suitable for diagnosis. -SmallString<128> ExprReprForDiagnosis(ArraySubscriptExpr const *E, - CheckerContext &C) { +static std::string ExprReprForDiagnosis(ArraySubscriptExpr const *E, + CheckerContext &C) { SmallString<128> Buf; llvm::raw_svector_ostream Out(Buf); @@ -740,7 +740,7 @@ SmallString<128> ExprReprForDiagnosis(ArraySubscriptExpr const *E, Out << '\''; } - return Buf; + return std::string(Buf); } class StateIndexUpdateReporter { @@ -800,15 +800,16 @@ auto ArrayBoundChecker::performCheckArrayTypeIndex(const ArraySubscriptExpr *E, auto State = C.getState(); SValBuilder &SVB = C.getSValBuilder(); - auto const ArrayInfo = getArrayTypeInfo(SVB, E); - auto const Index = + std::optional<std::pair<QualType, nonloc::ConcreteInt>> const ArrayInfo = + getArrayTypeInfo(SVB, E); + std::optional<NonLoc> const Index = SVB.simplifySVal(State, C.getSVal(E->getIdx())).getAs<NonLoc>(); if (!ArrayInfo || !Index) return ConstantArrayIndexResult::Unknown; auto const &[ArrayType, ArraySize] = *ArrayInfo; - auto const ExprAsStr = ExprReprForDiagnosis(E, C); + std::string const ExprAsStr = ExprReprForDiagnosis(E, C); bool const IsFakeFAM = isFakeFlexibleArrays(E); StateIndexUpdateReporter SUR(ExprAsStr, ArrayType, *Index, ArraySize); From 3c20250d7fb6c7d8c90c9d006f2fcccef1d31145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20=C3=81lvarez=20Ayll=C3=B3n?= <alejandro.alva...@sonarsource.com> Date: Thu, 18 Sep 2025 16:42:15 +0200 Subject: [PATCH 4/6] Remove redundant helper FAM function. Split tests. --- .../Checkers/ArrayBoundChecker.cpp | 52 +-------- clang/test/Analysis/ArrayBound/brief-tests.c | 106 ------------------ .../ArrayBound/flexible-array-members.c | 101 +++++++++++++++++ .../test/Analysis/ArrayBound/verbose-tests.c | 24 ++-- 4 files changed, 115 insertions(+), 168 deletions(-) create mode 100644 clang/test/Analysis/ArrayBound/flexible-array-members.c diff --git a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp index d0d048b2842be..4b7228b65efab 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp @@ -675,54 +675,11 @@ static Messages getOOBIndexMessage(StringRef Name, NonLoc Index, std::string(Buf)}; } -// "True" flexible array members do not specify any size (`int elems[];`) -// However, some projects use "fake flexible arrays" (aka "struct hack"), where -// they specify a size of 0 or 1 to work around a compiler limitation. -// "True" flexible array members are `IncompleteArrayType` and will be skipped -// by `performCheckArrayTypeIndex`. We need an heuristic to identify "fake" -// ones. -static bool isFakeFlexibleArrays(const ArraySubscriptExpr *E) { - auto getFieldDecl = [](ArraySubscriptExpr const *array) -> FieldDecl * { - const Expr *BaseExpr = array->getBase()->IgnoreParenImpCasts(); - if (const MemberExpr *ME = dyn_cast<MemberExpr>(BaseExpr)) { - return dyn_cast<FieldDecl>(ME->getMemberDecl()); - } - return nullptr; - }; - auto const isLastField = [](RecordDecl const *Parent, - FieldDecl const *Field) { - const FieldDecl *LastField = nullptr; - for (const FieldDecl *F : Parent->fields()) { - LastField = F; - } - - return (LastField == Field); - }; - // We expect placeholder constant arrays to have size 0 or 1. - auto maybeConstArrayPlaceholder = [](QualType Type) { - const ConstantArrayType *CAT = - dyn_cast<ConstantArrayType>(Type->getAsArrayTypeUnsafe()); - return CAT && CAT->getSize().getZExtValue() <= 1; - }; - - if (FieldDecl const *Field = getFieldDecl(E)) { - if (!maybeConstArrayPlaceholder(Field->getType())) - return false; - - const RecordDecl *Parent = Field->getParent(); - return Parent && (Parent->isUnion() || isLastField(Parent, Field)); - } - - return false; -} - // Generate a representation of `Expr` suitable for diagnosis. -static std::string ExprReprForDiagnosis(ArraySubscriptExpr const *E, - CheckerContext &C) { +static std::string ExprReprForDiagnosis(Expr const *Base, CheckerContext &C) { SmallString<128> Buf; llvm::raw_svector_ostream Out(Buf); - auto const *Base = E->getBase()->IgnoreParenImpCasts(); switch (Base->getStmtClass()) { case Stmt::MemberExprClass: Out << "the field '"; @@ -809,8 +766,11 @@ auto ArrayBoundChecker::performCheckArrayTypeIndex(const ArraySubscriptExpr *E, auto const &[ArrayType, ArraySize] = *ArrayInfo; - std::string const ExprAsStr = ExprReprForDiagnosis(E, C); - bool const IsFakeFAM = isFakeFlexibleArrays(E); + Expr const *BaseExpr = E->getBase()->IgnoreImpCasts(); + std::string const ExprAsStr = ExprReprForDiagnosis(BaseExpr, C); + bool const IsFakeFAM = BaseExpr->isFlexibleArrayMemberLike( + C.getASTContext(), C.getLangOpts().getStrictFlexArraysLevel(), + /*IgnoreTemplateOrMacroSubstitution=*/true); StateIndexUpdateReporter SUR(ExprAsStr, ArrayType, *Index, ArraySize); diff --git a/clang/test/Analysis/ArrayBound/brief-tests.c b/clang/test/Analysis/ArrayBound/brief-tests.c index c8206a3cadace..e5a90b282bef1 100644 --- a/clang/test/Analysis/ArrayBound/brief-tests.c +++ b/clang/test/Analysis/ArrayBound/brief-tests.c @@ -1,8 +1,4 @@ // RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,security.ArrayBound,debug.ExprInspection -verify %s -// RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,security.ArrayBound,debug.ExprInspection \ -// RUN: -DWARN_FLEXIBLE_ARRAY -analyzer-config security.ArrayBound:EnableFakeFlexibleArrayWarn=true -verify %s - -#include "../Inputs/system-header-simulator-for-malloc.h" // Miscellaneous tests for `security.ArrayBound` where we only test the // presence or absence of a bug report. If a test doesn't fit in a more @@ -11,8 +7,6 @@ void clang_analyzer_value(int); void clang_analyzer_eval(int); -void clang_analyzer_warnIfReached(); - // Tests doing an out-of-bounds access after the end of an array using: // - constant integer index @@ -331,106 +325,6 @@ int unsigned_index_quirk_inner(unsigned char index) { return array[index]; // no warning } -struct WithTrailingArray { - int nargs; - int args[1]; -}; - -int use_trailing(struct WithTrailingArray *p) { -#ifdef WARN_FLEXIBLE_ARRAY - // expected-warning@+2 {{Potential out of bound access to the field 'args', which may be a 'flexible array member'}} -#endif - int x = p->args[3]; - // No sink - clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} - return x; -} - -struct NotReallyTrailing { - int array[1]; - int more_data; -}; - -int use_non_trailing(struct NotReallyTrailing *p) { - int x = p->array[3]; // expected-warning {{Out of bound access to memory after the end of the field 'array'}} - clang_analyzer_warnIfReached(); // sink, no warning - return x; -} - -struct TrailingNoException { - int n; - int some[2]; -}; - -int use_trailing_no_exception(struct TrailingNoException *p) { - int x = p->some[10]; // expected-warning {{Out of bound access to memory after the end of the field 'some'}} - clang_analyzer_warnIfReached(); // sink, no warning - return x; -} - -int regular_one_array() { - int array[1]; - int x = array[2]; // expected-warning {{Out of bound access to memory after the end of 'array'}} - clang_analyzer_warnIfReached(); // sink, no warning - return x; -} - -struct WithTrailing0 { - int nargs; - int args[0]; -}; - -int use_trailing0(struct WithTrailing0 *p) { -#ifdef WARN_FLEXIBLE_ARRAY - // expected-warning@+2 {{Potential out of bound access to the field 'args', which may be a 'flexible array member'}} -#endif - int x = p->args[10]; // no warning - // No sink - clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} - return x; -} - -struct RealFlexibleArray { - int nargs; - int args[]; -}; - -// Trailing array, allocation unknown -int use_real_trailing(struct RealFlexibleArray *p) { - int x = p->args[3]; // no warning - // No sink - clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} - return x; -} - -int use_allocated_fam() { - struct RealFlexibleArray *p = malloc(sizeof(struct RealFlexibleArray) + 10 * sizeof(int)); - // This is a false negative. - // `ArrayBoundCheckers` queries for the extent of the memory region - // `Element{SymRegion{conj_$2{void *, LC1, S1173, #1}},0 S64b,struct RealFlexibleArray}.args`, - // which is unknown. Either `ArrayBoundCheckers`, or `DynamicExtent.cpp` should - // learn how to handle this pattern. - int x = p->args[30]; - free(p); - return x; -} - -// Pattern used in GCC -union FlexibleArrayUnion { - int args[1]; - struct {int x, y, z;}; -}; - -int use_union(union FlexibleArrayUnion *p) { -#ifdef WARN_FLEXIBLE_ARRAY - // expected-warning@+2 {{Potential out of bound access to the field 'args', which may be a 'flexible array member'}} -#endif - int x = p->args[2]; - // No sink - clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} - return x; -} - void custom_unreachable(); int get_index(char b) { diff --git a/clang/test/Analysis/ArrayBound/flexible-array-members.c b/clang/test/Analysis/ArrayBound/flexible-array-members.c new file mode 100644 index 0000000000000..f0b20935481d4 --- /dev/null +++ b/clang/test/Analysis/ArrayBound/flexible-array-members.c @@ -0,0 +1,101 @@ +// RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,security.ArrayBound,debug.ExprInspection \ +// RUN: -fstrict-flex-arrays=0 -DSTRICT_FLEX=0 \ +// RUN: -verify %s +// +// RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,security.ArrayBound,debug.ExprInspection \ +// RUN: -fstrict-flex-arrays=1 -DSTRICT_FLEX=1 \ +// RUN: -verify %s +// +// RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,security.ArrayBound,debug.ExprInspection \ +// RUN: -fstrict-flex-arrays=2 -DSTRICT_FLEX=2 \ +// RUN: -verify %s +// +// RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,security.ArrayBound,debug.ExprInspection \ +// RUN: -fstrict-flex-arrays=3 -DSTRICT_FLEX=3 \ +// RUN: -verify %s + +#include "../Inputs/system-header-simulator-for-malloc.h" + +void clang_analyzer_warnIfReached(); + +struct RealFAM { + int size; + int args[]; +}; + +int use_fam(struct RealFAM *ptr) { + int x = ptr->args[1]; + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + return x; +} + +struct FAM0 { + int size; + int args[0]; +}; + +int use_fam0(struct FAM0 *ptr) { + int x = ptr->args[1]; +#if STRICT_FLEX > 2 + // expected-warning@-2 {{Out of bound access to memory after the end of the field 'args'}} +#else + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} +#endif + return x; +} + +struct FAM1 { + int size; + int args[1]; +}; + +int use_fam1(struct FAM1 *ptr) { + int x = ptr->args[2]; +#if STRICT_FLEX > 1 + // expected-warning@-2 {{Out of bound access to memory after the end of the field 'args'}} +#else + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} +#endif + return x; +} + +struct FAMN { + int size; + int args[64]; +}; + +int use_famn(struct FAMN *ptr) { + int x = ptr->args[128]; +#if STRICT_FLEX > 0 + // expected-warning@-2 {{Out of bound access to memory after the end of the field 'args'}} +#else + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} +#endif + return x; +} + +struct NotFAM { + int keys[1]; + int values[1]; +}; + +int use_not_fam(struct NotFAM *ptr) { + return ptr->keys[3]; // expected-warning {{Out of bound access to memory after the end of the field 'keys'}} +} + +// Pattern used in GCC +union FlexibleArrayUnion { + int args[1]; + struct {int x, y, z;}; +}; + + +int use_union(union FlexibleArrayUnion *p) { + int x = p->args[2]; +#if STRICT_FLEX > 1 + // expected-warning@-2 {{Out of bound access to memory after the end of the field 'args'}} +#else + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} +#endif + return x; +} diff --git a/clang/test/Analysis/ArrayBound/verbose-tests.c b/clang/test/Analysis/ArrayBound/verbose-tests.c index f23a0e1cde941..92b80483c637f 100644 --- a/clang/test/Analysis/ArrayBound/verbose-tests.c +++ b/clang/test/Analysis/ArrayBound/verbose-tests.c @@ -240,14 +240,18 @@ struct vec { double arrayInStruct(void) { return v.elems[64]; - // expected-warning@-1 {{Out of bound access to memory after the end of the field 'elems'}} - // expected-note@-2 {{Access of the field 'elems' at index 64, while it holds only 64 'double' elements}} +#if STRICT_FLEX >= 1 + // expected-warning@-2 {{Out of bound access to memory after the end of the field 'elems'}} + // expected-note@-3 {{Access of the field 'elems' at index 64, while it holds only 64 'double' elements}} +#endif } double arrayInStructPtr(struct vec *pv) { return pv->elems[64]; - // expected-warning@-1 {{Out of bound access to memory after the end of the field 'elems'}} - // expected-note@-2 {{Access of the field 'elems' at index 64, while it holds only 64 'double' elements}} +#if STRICT_FLEX >= 1 + // expected-warning@-2 {{Out of bound access to memory after the end of the field 'elems'}} + // expected-note@-3 {{Access of the field 'elems' at index 64, while it holds only 64 'double' elements}} +#endif } struct item { @@ -466,15 +470,3 @@ int test_case_long_elem_name() { // expected-warning@-1 {{Out of bound access to memory after the end of 'table'}} // expected-note@-2 {{Access of 'table' at index 55, while it holds only 10 'struct VERYLONGPR...' elements}} } - -struct What { - int x[0]; - int y; -}; - -int who() { - struct What who; - return who.x[1]; - // expected-warning@-1 {{Out of bound access to memory after the end of the field 'x'}} - // expected-note@-2 {{Access of the field 'x' at index 1, while it holds only 0 'int' elements}} -} From 2ecd83090b5fe9e01133715ef575689eb5d3cf8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20=C3=81lvarez=20Ayll=C3=B3n?= <alejandro.alva...@sonarsource.com> Date: Thu, 18 Sep 2025 17:09:29 +0200 Subject: [PATCH 5/6] Test EnableFakeFlexibleArrayWarn --- .../Analysis/ArrayBound/flexible-array-members.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/clang/test/Analysis/ArrayBound/flexible-array-members.c b/clang/test/Analysis/ArrayBound/flexible-array-members.c index f0b20935481d4..06721006f8b0b 100644 --- a/clang/test/Analysis/ArrayBound/flexible-array-members.c +++ b/clang/test/Analysis/ArrayBound/flexible-array-members.c @@ -11,6 +11,11 @@ // RUN: -verify %s // // RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,security.ArrayBound,debug.ExprInspection \ +// RUN: -fstrict-flex-arrays=2 -DSTRICT_FLEX=2 \ +// RUN: -DWARN_FLEXIBLE_ARRAY -analyzer-config security.ArrayBound:EnableFakeFlexibleArrayWarn=true \ +// RUN: -verify %s +// +// RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,security.ArrayBound,debug.ExprInspection \ // RUN: -fstrict-flex-arrays=3 -DSTRICT_FLEX=3 \ // RUN: -verify %s @@ -39,6 +44,9 @@ int use_fam0(struct FAM0 *ptr) { #if STRICT_FLEX > 2 // expected-warning@-2 {{Out of bound access to memory after the end of the field 'args'}} #else +#ifdef WARN_FLEXIBLE_ARRAY + // expected-warning@-5 {{Potential out of bound access to the field 'args', which may be a 'flexible array member'}} +#endif clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} #endif return x; @@ -86,10 +94,11 @@ int use_not_fam(struct NotFAM *ptr) { // Pattern used in GCC union FlexibleArrayUnion { int args[1]; - struct {int x, y, z;}; + struct { + int x, y, z; + }; }; - int use_union(union FlexibleArrayUnion *p) { int x = p->args[2]; #if STRICT_FLEX > 1 From 535111415bca6207ff861ae7068a9470cb3f6584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20=C3=81lvarez=20Ayll=C3=B3n?= <alejandro.alva...@sonarsource.com> Date: Thu, 18 Sep 2025 17:18:23 +0200 Subject: [PATCH 6/6] Make truncateWithEllipsis templated --- clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp index 4b7228b65efab..4ea5273127577 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp @@ -636,7 +636,10 @@ static Messages getNegativeIndexMessage(StringRef Name, Name, ArraySizeVal, IndexStr)}; } -static std::string truncateWithEllipsis(StringRef Str, size_t MaxLength) { +template <size_t MaxLength = 20> +static std::string truncateWithEllipsis(StringRef Str) { + static_assert(MaxLength > 4, "MaxLength must be long enough to hold a single " + "character plus the ellipsis"); if (Str.size() <= MaxLength) return Str.str(); @@ -661,8 +664,7 @@ static Messages getOOBIndexMessage(StringRef Name, NonLoc Index, Out << "an overflowing index"; } - std::string const ElemTypeStr = - truncateWithEllipsis(ElemType.getAsString(), 20); + std::string const ElemTypeStr = truncateWithEllipsis(ElemType.getAsString()); Out << ", while it holds only "; if (ExtentN != 1) _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits