1//== ArrayBoundChecker.cpp -------------------------------------------------==//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file defines security.ArrayBound, which is a path-sensitive checker
10// that looks for out of bounds access of memory regions.
11//
12//===----------------------------------------------------------------------===//
13
14#include "clang/AST/CharUnits.h"
15#include "clang/AST/ParentMapContext.h"
16#include "clang/StaticAnalyzer/Checkers/BoundsChecking.h"
17#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
18#include "clang/StaticAnalyzer/Checkers/Taint.h"
19#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
20#include "clang/StaticAnalyzer/Core/Checker.h"
21#include "clang/StaticAnalyzer/Core/CheckerManager.h"
22#include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h"
23#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
24#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
25#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
26#include "llvm/ADT/APSInt.h"
27#include "llvm/Support/FormatVariadic.h"
28#include "llvm/Support/raw_ostream.h"
29#include <optional>
30
31using namespace clang;
32using namespace ento;
33using namespace taint;
34using llvm::formatv;
35
36namespace {
37/// If `E` is an array subscript expression with a base that is "clean" (= not
38/// modified by pointer arithmetic = the beginning of a memory region), return
39/// it as a pointer to ArraySubscriptExpr; otherwise return nullptr.
40/// This helper function is used by two separate heuristics that are only valid
41/// in these "clean" cases.
42static const ArraySubscriptExpr *
43getAsCleanArraySubscriptExpr(const Expr *E, const CheckerContext &C) {
44 const auto *ASE = dyn_cast<ArraySubscriptExpr>(Val: E);
45 if (!ASE)
46 return nullptr;
47
48 const MemRegion *SubscriptBaseReg = C.getSVal(E: ASE->getBase()).getAsRegion();
49 if (!SubscriptBaseReg)
50 return nullptr;
51
52 // The base of the subscript expression is affected by pointer arithmetics,
53 // so we want to report byte offsets instead of indices and we don't want to
54 // activate the "index is unsigned -> cannot be negative" shortcut.
55 if (isa<ElementRegion>(Val: SubscriptBaseReg->StripCasts()))
56 return nullptr;
57
58 return ASE;
59}
60
61class SizeUnit {
62 QualType AsType;
63 int64_t AsCharUnits;
64
65 SizeUnit() : AsType(), AsCharUnits(1) {}
66
67public:
68 SizeUnit(QualType T, const ASTContext &ACtx)
69 : AsType(T), AsCharUnits(ACtx.getTypeSizeInChars(T).getQuantity()) {
70 assert(!T.isNull());
71 }
72
73 static SizeUnit bytes() { return SizeUnit(); }
74
75 bool isBytes() const { return AsType.isNull(); }
76
77 /// Return the element type that is "natural" for reporting out-of-bounds
78 /// memory access to \p ER.
79 static SizeUnit forElementRegion(const ElementRegion *ER,
80 const ASTContext &ACtx) {
81 return SizeUnit(ER->getElementType(), ACtx);
82 }
83
84 /// If `E` is a "clean" array subscript expression, return the type of the
85 /// accessed element; otherwise return 'Bytes' because that's the best (or
86 /// least bad) option for the assumption messages that use this.
87 /// FIXME: It is unfortunate that this heuristic differs from the heuristic
88 /// used for reporting assumption; but this difference is currently needed
89 /// due to the unfortunate phrasing of the assumption messages.
90 /// Get rid of this when the assumption note is rephrased and improved.
91 static SizeUnit forExpr(const Expr *E, const CheckerContext &C) {
92 const auto *ASE = getAsCleanArraySubscriptExpr(E, C);
93 if (!ASE)
94 return bytes();
95
96 return SizeUnit(ASE->getType(), C.getASTContext());
97 }
98
99 int64_t asCharUnits() const { return AsCharUnits; }
100
101 bool canExpress(std::optional<int64_t> Val) const {
102 return asCharUnits() && (!Val || !(*Val % asCharUnits()));
103 }
104
105 std::string asExtentDesc() const {
106 if (isBytes())
107 return "the extent of";
108 return formatv(Fmt: "the number of '{0}' elements in", Vals: AsType.getAsString());
109 }
110
111 std::string asElementName() const {
112 if (isBytes())
113 return "byte";
114 return formatv(Fmt: "'{0}' element", Vals: AsType.getAsString());
115 }
116};
117
118/// Strings that will be passed to the parameters 'desc' and 'fullDesc' of the
119/// constructor of 'PathSensitiveBugReport'.
120struct BugDescription {
121 std::string Short;
122 std::string Full;
123};
124
125// NOTE: The `ArraySubscriptExpr` and `UnaryOperator` callbacks are `PostStmt`
126// instead of `PreStmt` because the current implementation passes the whole
127// expression to `CheckerContext::getSVal()` which only works after the
128// symbolic evaluation of the expression. (To turn them into `PreStmt`
129// callbacks, we'd need to duplicate the logic that evaluates these
130// expressions.) The `MemberExpr` callback would work as `PreStmt` but it's
131// defined as `PostStmt` for the sake of consistency with the other callbacks.
132class ArrayBoundChecker : public Checker<check::PostStmt<ArraySubscriptExpr>,
133 check::PostStmt<UnaryOperator>,
134 check::PostStmt<MemberExpr>> {
135 BugType BT{this, "Out-of-bound access"};
136 BugType TaintBT{this, "Out-of-bound access", categories::TaintedData};
137
138 void handleAccessExpr(const Expr *E, CheckerContext &C) const;
139
140 void reportOOB(CheckerContext &C, ProgramStateRef ErrorState,
141 BugDescription Desc, NonLoc Offset,
142 std::optional<NonLoc> Extent, bool IsTaintBug = false) const;
143
144 static void markPartsInteresting(PathSensitiveBugReport &BR,
145 ProgramStateRef ErrorState, NonLoc Val,
146 bool MarkTaint);
147
148 static bool isFromCtypeMacro(const Expr *E, ASTContext &AC);
149
150 static bool isOffsetObviouslyNonnegative(const Expr *E, CheckerContext &C);
151
152 static bool isInAddressOf(const Stmt *S, ASTContext &AC);
153
154public:
155 void checkPostStmt(const ArraySubscriptExpr *E, CheckerContext &C) const {
156 handleAccessExpr(E, C);
157 }
158 void checkPostStmt(const UnaryOperator *E, CheckerContext &C) const {
159 if (E->getOpcode() == UO_Deref)
160 handleAccessExpr(E, C);
161 }
162 void checkPostStmt(const MemberExpr *E, CheckerContext &C) const {
163 if (E->isArrow())
164 handleAccessExpr(E: E->getBase(), C);
165 }
166};
167
168} // anonymous namespace
169
170/// Return true if information about the value of \p SV can put constraints
171/// on some symbol which is interesting within the bug report \p BR.
172/// In particular, this returns true when \p SV is interesting within \p BR;
173/// but it also returns true if \p SV is an expression that contains integer
174/// constants and a single symbolic operand which is interesting (in \p BR).
175/// We need to use this instead of plain `BR.isInteresting()` because if we
176/// are analyzing code like
177/// int array[10];
178/// int f(int arg) {
179/// return array[arg] && array[arg + 10];
180/// }
181/// then the byte offsets are `arg * 4` and `(arg + 10) * 4`, which are not
182/// sub-expressions of each other (but `getSimplifiedOffsets` is smart enough
183/// to detect this out of bounds access).
184static bool isDeterminedByInterestingSymbol(SVal SV,
185 PathSensitiveBugReport &BR) {
186 SymbolRef Sym = SV.getAsSymbol();
187 if (!Sym)
188 return false;
189 for (SymbolRef PartSym : Sym->symbols()) {
190 // The interestingess mark may appear on any layer as we're stripping off
191 // the SymIntExpr, UnarySymExpr etc. layers...
192 if (BR.isInteresting(sym: PartSym))
193 return true;
194 // ...but if both sides of the expression are symbolic, then there is no
195 // practical algorithm to produce separate constraints for the two
196 // operands (from the single combined result).
197 if (isa<SymSymExpr>(Val: PartSym))
198 return false;
199 }
200 return false;
201}
202
203static int64_t getElementSize(const ElementRegion *ER, SValBuilder &SVB) {
204 QualType ElemType = ER->getElementType();
205
206 assert(!ElemType->isIncompleteType() && "ElemType cannot be incomplete");
207
208 return SVB.getContext().getTypeSizeInChars(T: ElemType).getQuantity();
209}
210
211/// For a given \p CurRegion that can be represented as a symbolic expression
212/// Arr[Idx] (or perhaps Arr[Idx1][Idx2] etc.), return the parent memory block
213/// Arr and the distance of Location from the beginning of Arr (expressed in a
214/// NonLoc that specifies the number of CharUnits). Returns nullopt when these
215/// cannot be determined.
216static std::optional<std::pair<const SubRegion *, NonLoc>>
217computeOffset(ProgramStateRef State, SValBuilder &SVB,
218 const ElementRegion *CurRegion) {
219 QualType T = SVB.getArrayIndexType();
220 auto EvalBinOp = [&SVB, State, T](BinaryOperatorKind Op, NonLoc L, NonLoc R) {
221 // We will use this utility to add and multiply values.
222 return SVB.evalBinOpNN(state: State, op: Op, lhs: L, rhs: R, resultTy: T).getAs<NonLoc>();
223 };
224
225 const SubRegion *OwnerRegion = nullptr;
226 std::optional<NonLoc> Offset = SVB.makeZeroArrayIndex();
227
228 while (CurRegion) {
229 const auto Index = CurRegion->getIndex().getAs<NonLoc>();
230 if (!Index)
231 return std::nullopt;
232
233 // Calculate Delta = Index * sizeof(ElemType).
234 NonLoc Size = SVB.makeArrayIndex(idx: getElementSize(ER: CurRegion, SVB));
235 auto Delta = EvalBinOp(BO_Mul, *Index, Size);
236 if (!Delta)
237 return std::nullopt;
238
239 // Perform Offset += Delta.
240 Offset = EvalBinOp(BO_Add, *Offset, *Delta);
241 if (!Offset)
242 return std::nullopt;
243
244 OwnerRegion = CurRegion->getSuperRegion()->getAs<SubRegion>();
245 // When this is just another ElementRegion layer, we need to continue the
246 // offset calculations:
247 CurRegion = dyn_cast_or_null<ElementRegion>(Val: OwnerRegion);
248 }
249
250 if (OwnerRegion)
251 return std::make_pair(x&: OwnerRegion, y&: *Offset);
252
253 return std::nullopt;
254}
255
256static std::optional<int64_t> getConcreteValue(NonLoc SV) {
257 if (auto ConcreteVal = SV.getAs<nonloc::ConcreteInt>()) {
258 return ConcreteVal->getValue()->tryExtValue();
259 }
260 return std::nullopt;
261}
262
263static std::optional<int64_t> getConcreteValue(std::optional<NonLoc> SV) {
264 return SV ? getConcreteValue(SV: *SV) : std::nullopt;
265}
266
267static StringRef getAdjective(const bounds::CheckResult &R) {
268 return (R.mayUnderflow()
269 ? (R.mayOverflow() ? "a negative or overflowing" : "a negative")
270 : (R.mayOverflow() ? "an overflowing" : "a valid"));
271}
272
273static StringRef getPreposition(const bounds::CheckResult &R) {
274 return (R.mayUnderflow() ? (R.mayOverflow() ? "around" : "preceding")
275 : (R.mayOverflow() ? "after the end of" : "within"));
276}
277
278static BugDescription describeInvalidAccess(bounds::CheckResult Res,
279 StringRef RegName, SizeUnit SU) {
280 assert(Res.mayBeInvalid());
281
282 std::optional<int64_t> OffsetN = getConcreteValue(SV: Res.getOffset());
283 std::optional<int64_t> ExtentN =
284 getConcreteValue(SV: Res.getExtentIfMayOverflow());
285
286 if (SU.canExpress(Val: OffsetN) && SU.canExpress(Val: ExtentN)) {
287 if (OffsetN)
288 *OffsetN /= SU.asCharUnits();
289 if (ExtentN)
290 *ExtentN /= SU.asCharUnits();
291 } else {
292 // Fall back to reporting the offsets in bytes.
293 SU = SizeUnit::bytes();
294 }
295
296 StringRef OffsetOrIndex = SU.isBytes() ? "byte offset" : "index";
297
298 SmallString<256> Buf;
299 llvm::raw_svector_ostream Out(Buf);
300 Out << "Access of ";
301 if (OffsetN && !ExtentN && !SU.isBytes()) {
302 // If the offset is reported as an index, then the report must mention the
303 // element type (because it is not always clear from the code). It's more
304 // natural to mention the element type later where the extent is described,
305 // but if the extent is unknown/irrelevant, then the element type can be
306 // inserted into the message at this point.
307 Out << SU.asElementName() << " in ";
308 }
309 Out << RegName << " at ";
310 if (OffsetN) {
311 if (Res.mayUnderflow() && !Res.mayOverflow())
312 Out << "negative ";
313 Out << OffsetOrIndex << " " << *OffsetN;
314 } else {
315 Out << getAdjective(R: Res) << " " << OffsetOrIndex;
316 }
317 if (ExtentN) {
318 Out << ", while it holds only ";
319 if (*ExtentN != 1)
320 Out << *ExtentN;
321 else
322 Out << "a single";
323
324 Out << ' ' << SU.asElementName();
325
326 if (*ExtentN != 1)
327 Out << "s";
328 }
329
330 return {.Short: formatv(Fmt: "Out of bound access to memory {0} {1}", Vals: getPreposition(R: Res),
331 Vals&: RegName),
332 .Full: std::string(Buf)};
333}
334
335static BugDescription describeTaintBug(bounds::CheckResult Res,
336 StringRef RegName,
337 StringRef OffsetName) {
338 assert(Res.mayBeInvalid());
339 return {.Short: formatv(Fmt: "Potential out of bound access to {0} with tainted {1}",
340 Vals&: RegName, Vals&: OffsetName),
341 .Full: formatv(Fmt: "Access of {0} with a tainted {1} that may be{2}{3}{4}",
342 Vals&: RegName, Vals&: OffsetName, Vals: Res.mayUnderflow() ? " negative" : "",
343 Vals: (Res.mayUnderflow() && Res.mayOverflow()) ? " or" : "",
344 Vals: Res.mayOverflow() ? " too large" : "")};
345}
346
347/// When the access was ambiguous (that is, mayBeInBounds() && mayBeInvalid()),
348/// returns the note "assuming in bounds" note that is relevant for the bug
349/// report \p BR. When the access wasn't ambiguous or the the assumption is
350/// irrelevant for \p BR, this returns the empty string (which signifies "do
351/// not emit a note tag" when returned by a note tag callback).
352static std::string getAssumptionNote(bounds::CheckResult Res,
353 PathSensitiveBugReport &BR,
354 StringRef RegName, SizeUnit SU) {
355 bool ShouldReportNonNegative = Res.mayUnderflow();
356 if (!isDeterminedByInterestingSymbol(SV: Res.getOffset(), BR)) {
357 std::optional<NonLoc> E = Res.getExtentIfMayOverflow();
358 if (E && isDeterminedByInterestingSymbol(SV: *E, BR)) {
359 // Even if the byte offset isn't interesting (e.g. it's a constant value),
360 // the assumption can still be interesting if it provides information
361 // about an interesting symbolic upper bound.
362 ShouldReportNonNegative = false;
363 } else {
364 // We don't have anything interesting, don't report the assumption.
365 return "";
366 }
367 }
368
369 std::optional<int64_t> OffsetN = getConcreteValue(SV: Res.getOffset());
370 std::optional<int64_t> ExtentN =
371 getConcreteValue(SV: Res.getExtentIfMayOverflow());
372
373 if (SU.canExpress(Val: OffsetN) && SU.canExpress(Val: ExtentN)) {
374 if (OffsetN)
375 *OffsetN /= SU.asCharUnits();
376 if (ExtentN)
377 *ExtentN /= SU.asCharUnits();
378 } else {
379 // Fall back to reporting the offsets in bytes.
380 SU = SizeUnit::bytes();
381 }
382
383 SmallString<256> Buf;
384 llvm::raw_svector_ostream Out(Buf);
385 Out << "Assuming ";
386 if (!SU.isBytes()) {
387 Out << "index ";
388 if (OffsetN)
389 Out << "'" << OffsetN << "' ";
390 } else if (Res.mayOverflow()) {
391 Out << "byte offset ";
392 if (OffsetN)
393 Out << "'" << OffsetN << "' ";
394 } else {
395 Out << "offset ";
396 }
397
398 Out << "is";
399 if (ShouldReportNonNegative) {
400 Out << " non-negative";
401 }
402 if (Res.mayOverflow()) {
403 if (ShouldReportNonNegative)
404 Out << " and";
405 Out << " less than ";
406 if (ExtentN)
407 Out << *ExtentN << ", ";
408 Out << SU.asExtentDesc() << ' ' << RegName;
409 }
410 return std::string(Out.str());
411}
412
413void ArrayBoundChecker::handleAccessExpr(const Expr *E,
414 CheckerContext &C) const {
415 ASTContext &ACtx = C.getASTContext();
416 const ElementRegion *AccessedER =
417 dyn_cast_or_null<ElementRegion>(Val: C.getSVal(E).getAsRegion());
418 if (!AccessedER)
419 return;
420
421 // The header ctype.h (from e.g. glibc) implements the isXXXXX() macros as
422 // #define isXXXXX(arg) (LOOKUP_TABLE[arg] & BITMASK_FOR_XXXXX)
423 // and incomplete analysis of these leads to false positives. As even
424 // accurate reports would be confusing for the users, just disable reports
425 // from these macros:
426 if (isFromCtypeMacro(E, AC&: ACtx))
427 return;
428
429 ProgramStateRef State = C.getState();
430 SValBuilder &SVB = C.getSValBuilder();
431
432 const std::optional<std::pair<const SubRegion *, NonLoc>> &RawOffset =
433 computeOffset(State, SVB, CurRegion: AccessedER);
434
435 if (!RawOffset)
436 return;
437
438 auto [Reg, ByteOffset] = *RawOffset;
439
440 const MemSpaceRegion *Space = Reg->getMemorySpace(State);
441 auto Extent = getDynamicExtent(State, MR: Reg, SVB).getAs<NonLoc>();
442
443 // A symbolic region in unknown space represents an unknown pointer that
444 // may point into the middle of an array, so we don't look for underflows.
445 // Both conditions are significant because we want to check underflows in
446 // symbolic regions on the heap (which may be introduced by checkers like
447 // MallocChecker that call SValBuilder::getConjuredHeapSymbolVal()) and
448 // non-symbolic regions (e.g. a field subregion of a symbolic region) in
449 // unknown space.
450
451 bounds::CheckFlags Flags = {
452 /*CheckUnderflow=*/!(isa<SymbolicRegion>(Val: Reg) &&
453 isa<UnknownSpaceRegion>(Val: Space)),
454 /*OffsetObviouslyNonnegative=*/isOffsetObviouslyNonnegative(E, C),
455 /*AlsoAcceptEquality=*/(getElementSize(ER: AccessedER, SVB) == 0)};
456
457 bounds::CheckResult Res = checkBounds(State, SVB, Offset: ByteOffset, Extent, Flags);
458
459 if (Res.isCorruptedState()) {
460 C.addSink();
461 return;
462 }
463
464 std::string RegName =
465 Reg->getDescriptiveName(/*UseQuotes=*/true, /*AllowFallback=*/true);
466
467 const NoteTag *T = nullptr;
468 if (Res.mayBeInvalid()) {
469 if (!Res.mayBeInBounds()) {
470 if (isa<ArraySubscriptExpr>(Val: E) && isInAddressOf(S: E, AC&: ACtx) && Extent) {
471 // Recognize and accept the idiomatic `&array[size]` expression that
472 // forms the past-the-end pointer without actually dereferencing it.
473 auto [EqualsToThreshold, NotEqualToThreshold] =
474 bounds::compareValueToThreshold(State, SVB, Value: ByteOffset, Threshold: *Extent,
475 CmpKind: bounds::Comparison::EQ);
476 if (EqualsToThreshold && !NotEqualToThreshold) {
477 C.addTransition(State: EqualsToThreshold);
478 return;
479 }
480 }
481
482 SizeUnit SU = SizeUnit::forElementRegion(ER: AccessedER, ACtx);
483 BugDescription Desc = describeInvalidAccess(Res, RegName, SU);
484 reportOOB(C, ErrorState: State, Desc, Offset: ByteOffset, Extent: Res.getExtentIfMayOverflow());
485 return;
486 }
487
488 if (isTainted(State, V: ByteOffset)) {
489 // Diagnostic detail: saying "tainted offset" is always correct, but
490 // the common case is that 'idx' is tainted in 'arr[idx]' and then it's
491 // nicer to say "tainted index".
492 StringRef OffsetName = "offset";
493 if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(Val: E))
494 if (isTainted(State, E: ASE->getIdx(), SF: C.getStackFrame()))
495 OffsetName = "index";
496
497 BugDescription Desc = describeTaintBug(Res, RegName, OffsetName);
498 reportOOB(C, ErrorState: State, Desc, Offset: ByteOffset, Extent: Res.getExtentIfMayOverflow(),
499 /*IsTaintBug=*/true);
500 return;
501 }
502
503 SizeUnit SU = SizeUnit::forExpr(E, C);
504 T = C.getNoteTag(
505 Cb: [Res, RegName, SU](PathSensitiveBugReport &BR) -> std::string {
506 return getAssumptionNote(Res, BR, RegName, SU);
507 });
508 }
509
510 C.addTransition(State: Res.getInBoundsState(), Tag: T);
511}
512
513void ArrayBoundChecker::markPartsInteresting(PathSensitiveBugReport &BR,
514 ProgramStateRef ErrorState,
515 NonLoc Val, bool MarkTaint) {
516 if (SymbolRef Sym = Val.getAsSymbol()) {
517 // If the offset is a symbolic value, iterate over its "parts" with
518 // `SymExpr::symbols()` and mark each of them as interesting.
519 // For example, if the offset is `x*4 + y` then we put interestingness onto
520 // the SymSymExpr `x*4 + y`, the SymIntExpr `x*4` and the two data symbols
521 // `x` and `y`.
522 for (SymbolRef PartSym : Sym->symbols())
523 BR.markInteresting(sym: PartSym);
524 }
525
526 if (MarkTaint) {
527 // If the issue that we're reporting depends on the taintedness of the
528 // offset, then put interestingness onto symbols that could be the origin
529 // of the taint. Note that this may find symbols that did not appear in
530 // `Sym->symbols()` (because they're only loosely connected to `Val`).
531 for (SymbolRef Sym : getTaintedSymbols(State: ErrorState, V: Val))
532 BR.markInteresting(sym: Sym);
533 }
534}
535
536void ArrayBoundChecker::reportOOB(CheckerContext &C, ProgramStateRef ErrorState,
537 BugDescription Desc, NonLoc Offset,
538 std::optional<NonLoc> Extent,
539 bool IsTaintBug /*=false*/) const {
540
541 ExplodedNode *ErrorNode = C.generateErrorNode(State: ErrorState);
542 if (!ErrorNode)
543 return;
544
545 auto BR = std::make_unique<PathSensitiveBugReport>(
546 args: IsTaintBug ? TaintBT : BT, args&: Desc.Short, args&: Desc.Full, args&: ErrorNode);
547
548 // FIXME: ideally we would just call trackExpressionValue() and that would
549 // "do the right thing": mark the relevant symbols as interesting, track the
550 // control dependencies and statements storing the relevant values and add
551 // helpful diagnostic pieces. However, right now trackExpressionValue() is
552 // a heap of unreliable heuristics, so it would cause several issues:
553 // - Interestingness is not applied consistently, e.g. if `array[x+10]`
554 // causes an overflow, then `x` is not marked as interesting.
555 // - We get irrelevant diagnostic pieces, e.g. in the code
556 // `int *p = (int*)malloc(2*sizeof(int)); p[3] = 0;`
557 // it places a "Storing uninitialized value" note on the `malloc` call
558 // (which is technically true, but irrelevant).
559 // If trackExpressionValue() becomes reliable, it should be applied instead
560 // of this custom markPartsInteresting().
561 markPartsInteresting(BR&: *BR, ErrorState, Val: Offset, MarkTaint: IsTaintBug);
562 if (Extent)
563 markPartsInteresting(BR&: *BR, ErrorState, Val: *Extent, MarkTaint: IsTaintBug);
564
565 C.emitReport(R: std::move(BR));
566}
567
568bool ArrayBoundChecker::isFromCtypeMacro(const Expr *E, ASTContext &ACtx) {
569 SourceLocation Loc = E->getBeginLoc();
570 if (!Loc.isMacroID())
571 return false;
572
573 StringRef MacroName = Lexer::getImmediateMacroName(
574 Loc, SM: ACtx.getSourceManager(), LangOpts: ACtx.getLangOpts());
575
576 if (MacroName.size() < 7 || MacroName[0] != 'i' || MacroName[1] != 's')
577 return false;
578
579 return ((MacroName == "isalnum") || (MacroName == "isalpha") ||
580 (MacroName == "isblank") || (MacroName == "isdigit") ||
581 (MacroName == "isgraph") || (MacroName == "islower") ||
582 (MacroName == "isnctrl") || (MacroName == "isprint") ||
583 (MacroName == "ispunct") || (MacroName == "isspace") ||
584 (MacroName == "isupper") || (MacroName == "isxdigit"));
585}
586
587bool ArrayBoundChecker::isOffsetObviouslyNonnegative(const Expr *E,
588 CheckerContext &C) {
589 const ArraySubscriptExpr *ASE = getAsCleanArraySubscriptExpr(E, C);
590 if (!ASE)
591 return false;
592 return ASE->getIdx()->getType()->isUnsignedIntegerOrEnumerationType();
593}
594
595bool ArrayBoundChecker::isInAddressOf(const Stmt *S, ASTContext &ACtx) {
596 ParentMapContext &ParentCtx = ACtx.getParentMapContext();
597 do {
598 const DynTypedNodeList Parents = ParentCtx.getParents(Node: *S);
599 if (Parents.empty())
600 return false;
601 S = Parents[0].get<Stmt>();
602 } while (isa_and_nonnull<ParenExpr, ImplicitCastExpr>(Val: S));
603 const auto *UnaryOp = dyn_cast_or_null<UnaryOperator>(Val: S);
604 return UnaryOp && UnaryOp->getOpcode() == UO_AddrOf;
605}
606
607void ento::registerArrayBoundChecker(CheckerManager &mgr) {
608 mgr.registerChecker<ArrayBoundChecker>();
609}
610
611bool ento::shouldRegisterArrayBoundChecker(const CheckerManager &mgr) {
612 return true;
613}
614