Skip to content

Commit db0723c

Browse files
committed
[analyzer] Show element count in ArrayBound underflow reports
The underflow reports of checker security.ArrayBound already displayed the (negative) byte offset of the accessed location; but those numbers were sometimes a bit hard to decipher, so I'm extending the message to also display this offset as a multiple of the size of the accessed element. This logic is currently inactive when the byte offset is not an integer multiple of the size of the accessed element -- primarily because it would be a bit cumbersome to report the division and the remainder. This change only affects the messages; the checker will report the same issues before and after this commit.
1 parent 29b6433 commit db0723c

File tree

2 files changed

+64
-20
lines changed

2 files changed

+64
-20
lines changed

clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -389,15 +389,26 @@ static std::optional<int64_t> getConcreteValue(std::optional<NonLoc> SV) {
389389
}
390390

391391
static Messages getPrecedesMsgs(const MemSpaceRegion *Space,
392-
const SubRegion *Region, NonLoc Offset) {
393-
std::string RegName = getRegionName(Space, Region), OffsetStr = "";
392+
const SubRegion *Region, NonLoc Offset,
393+
QualType ElemType, int64_t ElemSize) {
394+
std::string RegName = getRegionName(Space, Region);
394395

395-
if (auto ConcreteOffset = getConcreteValue(Offset))
396+
std::string OffsetStr = "", ElemInfoStr = "";
397+
if (std::optional<int64_t> ConcreteOffset = getConcreteValue(Offset)) {
396398
OffsetStr = formatv(" {0}", ConcreteOffset);
399+
if (*ConcreteOffset % ElemSize == 0) {
400+
int64_t Count = *ConcreteOffset / ElemSize;
401+
if (Count != -1)
402+
ElemInfoStr =
403+
formatv(" = {0} * sizeof({1})", Count, ElemType.getAsString());
404+
else
405+
ElemInfoStr = formatv(" = -sizeof({0})", ElemType.getAsString());
406+
}
407+
}
397408

398-
return {
399-
formatv("Out of bound access to memory preceding {0}", RegName),
400-
formatv("Access of {0} at negative byte offset{1}", RegName, OffsetStr)};
409+
return {formatv("Out of bound access to memory preceding {0}", RegName),
410+
formatv("Access of {0} at negative byte offset{1}{2}", RegName,
411+
OffsetStr, ElemInfoStr)};
401412
}
402413

403414
/// Try to divide `Val1` and `Val2` (in place) by `Divisor` and return true if
@@ -419,20 +430,15 @@ static bool tryDividePair(std::optional<int64_t> &Val1,
419430
return true;
420431
}
421432

422-
static Messages getExceedsMsgs(ASTContext &ACtx, const MemSpaceRegion *Space,
433+
static Messages getExceedsMsgs(const MemSpaceRegion *Space,
423434
const SubRegion *Region, NonLoc Offset,
424-
NonLoc Extent, SVal Location,
425-
bool AlsoMentionUnderflow) {
435+
NonLoc Extent, bool AlsoMentionUnderflow,
436+
QualType ElemType, int64_t ElemSize) {
426437
std::string RegName = getRegionName(Space, Region);
427-
const auto *EReg = Location.getAsRegion()->getAs<ElementRegion>();
428-
assert(EReg && "this checker only handles element access");
429-
QualType ElemType = EReg->getElementType();
430438

431439
std::optional<int64_t> OffsetN = getConcreteValue(Offset);
432440
std::optional<int64_t> ExtentN = getConcreteValue(Extent);
433441

434-
int64_t ElemSize = ACtx.getTypeSizeInChars(ElemType).getQuantity();
435-
436442
bool UseByteOffsets = !tryDividePair(OffsetN, ExtentN, ElemSize);
437443
const char *OffsetOrIndex = UseByteOffsets ? "byte offset" : "index";
438444

@@ -585,6 +591,13 @@ void ArrayBoundChecker::performCheck(const Expr *E, CheckerContext &C) const {
585591
if (!RawOffset)
586592
return;
587593

594+
const auto *EReg = Location.getAsRegion()->getAs<ElementRegion>();
595+
assert(EReg && "this checker only handles element access");
596+
QualType ElemType = EReg->getElementType();
597+
598+
int64_t ElemSize =
599+
C.getASTContext().getTypeSizeInChars(ElemType).getQuantity();
600+
588601
auto [Reg, ByteOffset] = *RawOffset;
589602

590603
// The state updates will be reported as a single note tag, which will be
@@ -635,7 +648,8 @@ void ArrayBoundChecker::performCheck(const Expr *E, CheckerContext &C) const {
635648
} else {
636649
if (!WithinLowerBound) {
637650
// ...and it cannot be valid (>= 0), so report an error.
638-
Messages Msgs = getPrecedesMsgs(Space, Reg, ByteOffset);
651+
Messages Msgs =
652+
getPrecedesMsgs(Space, Reg, ByteOffset, ElemType, ElemSize);
639653
reportOOB(C, PrecedesLowerBound, Msgs, ByteOffset, std::nullopt);
640654
return;
641655
}
@@ -678,8 +692,8 @@ void ArrayBoundChecker::performCheck(const Expr *E, CheckerContext &C) const {
678692
}
679693

680694
Messages Msgs =
681-
getExceedsMsgs(C.getASTContext(), Space, Reg, ByteOffset,
682-
*KnownSize, Location, AlsoMentionUnderflow);
695+
getExceedsMsgs(Space, Reg, ByteOffset, *KnownSize,
696+
AlsoMentionUnderflow, ElemType, ElemSize);
683697
reportOOB(C, ExceedsUpperBound, Msgs, ByteOffset, KnownSize);
684698
return;
685699
}

clang/test/Analysis/ArrayBound/verbose-tests.c

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,47 @@ int TenElements[10];
1111
void arrayUnderflow(void) {
1212
TenElements[-3] = 5;
1313
// expected-warning@-1 {{Out of bound access to memory preceding 'TenElements'}}
14-
// expected-note@-2 {{Access of 'TenElements' at negative byte offset -12}}
14+
// expected-note@-2 {{Access of 'TenElements' at negative byte offset -12 = -3 * sizeof(int)}}
1515
}
1616

1717
int underflowWithDeref(void) {
1818
int *p = TenElements;
1919
--p;
2020
return *p;
2121
// expected-warning@-1 {{Out of bound access to memory preceding 'TenElements'}}
22-
// expected-note@-2 {{Access of 'TenElements' at negative byte offset -4}}
22+
// expected-note@-2 {{Access of 'TenElements' at negative byte offset -4 = -sizeof(int)}}
2323
}
2424

25+
char underflowReportedAsChar(void) {
26+
// The "= -... * sizeof(type)" part uses the type of the accessed element
27+
// (here 'char'), not the type that appears in the declaration of the
28+
// original array (which would be 'int').
29+
return ((char *)TenElements)[-1];
30+
// expected-warning@-1 {{Out of bound access to memory preceding 'TenElements'}}
31+
// expected-note@-2 {{Access of 'TenElements' at negative byte offset -1 = -sizeof(char)}}
32+
}
33+
34+
struct TwoInts {
35+
int a, b;
36+
};
37+
38+
struct TwoInts underflowReportedAsStruct(void) {
39+
// Another case where the accessed type is used for reporting the offset.
40+
return *(struct TwoInts*)(TenElements - 4);
41+
// expected-warning@-1 {{Out of bound access to memory preceding 'TenElements'}}
42+
// expected-note@-2 {{Access of 'TenElements' at negative byte offset -16 = -2 * sizeof(struct TwoInts)}}
43+
}
44+
45+
struct TwoInts underflowOnlyByteOffset(void) {
46+
// In this case the negative byte offset is not a multiple of the size of the
47+
// accessed element, so the part "= -... * sizeof(type)" is omitted at the
48+
// end of the message.
49+
return *(struct TwoInts*)(TenElements - 3);
50+
// expected-warning@-1 {{Out of bound access to memory preceding 'TenElements'}}
51+
// expected-note-re@-2 {{Access of 'TenElements' at negative byte offset -12{{$}}}}
52+
}
53+
54+
2555
int rng(void);
2656
int getIndex(void) {
2757
switch (rng()) {
@@ -40,7 +70,7 @@ void gh86959(void) {
4070
while (rng())
4171
TenElements[getIndex()] = 10;
4272
// expected-warning@-1 {{Out of bound access to memory preceding 'TenElements'}}
43-
// expected-note@-2 {{Access of 'TenElements' at negative byte offset -688}}
73+
// expected-note@-2 {{Access of 'TenElements' at negative byte offset -688 = -172 * sizeof(int)}}
4474
}
4575

4676
int scanf(const char *restrict fmt, ...);

0 commit comments

Comments
 (0)