Skip to content

Commit b88023c

Browse files
committed
[analyzer][NFC] Use std::optional instead of custom "empty" state
This commit eliminates the uninitialized error state from the class RegionRawOffsetV2 (which is locally used by the Clang Static Analyzer checker alpha.security.ArrayBoundV2) and replaces its use with std::optional. Motivated by https://reviews.llvm.org/D148355#inline-1437928 Moreover, the code of RegionRawOffsetV2::computeOffset() is rearranged to clarify its behavior. The helper function getValue() was eliminated by picking a better initial value for the variable Offset; two other helper functions were replaced by the lambda function Calc() because this way it doesn't need to take the "context" objects as parameters. This reorganization revealed some surprising (but not outright buggy) behavior that's marked by a FIXME and will be revisited in a separate commit. Differential Revision: https://reviews.llvm.org/D149259
1 parent d9683a7 commit b88023c

File tree

1 file changed

+55
-88
lines changed

1 file changed

+55
-88
lines changed

clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp

Lines changed: 55 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -53,21 +53,17 @@ class ArrayBoundCheckerV2 :
5353
class RegionRawOffsetV2 {
5454
private:
5555
const SubRegion *baseRegion;
56-
SVal byteOffset;
57-
58-
RegionRawOffsetV2()
59-
: baseRegion(nullptr), byteOffset(UnknownVal()) {}
56+
NonLoc byteOffset;
6057

6158
public:
6259
RegionRawOffsetV2(const SubRegion *base, NonLoc offset)
6360
: baseRegion(base), byteOffset(offset) { assert(base); }
6461

65-
NonLoc getByteOffset() const { return byteOffset.castAs<NonLoc>(); }
62+
NonLoc getByteOffset() const { return byteOffset; }
6663
const SubRegion *getRegion() const { return baseRegion; }
6764

68-
static RegionRawOffsetV2 computeOffset(ProgramStateRef state,
69-
SValBuilder &svalBuilder,
70-
SVal location);
65+
static std::optional<RegionRawOffsetV2>
66+
computeOffset(ProgramStateRef State, SValBuilder &SVB, SVal Location);
7167

7268
void dump() const;
7369
void dumpToStream(raw_ostream &os) const;
@@ -169,16 +165,16 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad,
169165
ProgramStateRef state = checkerContext.getState();
170166

171167
SValBuilder &svalBuilder = checkerContext.getSValBuilder();
172-
const RegionRawOffsetV2 &rawOffset =
173-
RegionRawOffsetV2::computeOffset(state, svalBuilder, location);
168+
const std::optional<RegionRawOffsetV2> &RawOffset =
169+
RegionRawOffsetV2::computeOffset(state, svalBuilder, location);
174170

175-
if (!rawOffset.getRegion())
171+
if (!RawOffset)
176172
return;
177173

178-
NonLoc ByteOffset = rawOffset.getByteOffset();
174+
NonLoc ByteOffset = RawOffset->getByteOffset();
179175

180176
// CHECK LOWER BOUND
181-
const MemSpaceRegion *SR = rawOffset.getRegion()->getMemorySpace();
177+
const MemSpaceRegion *SR = RawOffset->getRegion()->getMemorySpace();
182178
if (!llvm::isa<UnknownSpaceRegion>(SR)) {
183179
// A pointer to UnknownSpaceRegion may point to the middle of
184180
// an allocated region.
@@ -199,7 +195,7 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad,
199195

200196
// CHECK UPPER BOUND
201197
DefinedOrUnknownSVal Size =
202-
getDynamicExtent(state, rawOffset.getRegion(), svalBuilder);
198+
getDynamicExtent(state, RawOffset->getRegion(), svalBuilder);
203199
if (auto KnownSize = Size.getAs<NonLoc>()) {
204200
auto [state_withinUpperBound, state_exceedsUpperBound] =
205201
compareValueToThreshold(state, ByteOffset, *KnownSize, svalBuilder);
@@ -307,84 +303,55 @@ void RegionRawOffsetV2::dumpToStream(raw_ostream &os) const {
307303
}
308304
#endif
309305

310-
// Lazily computes a value to be used by 'computeOffset'. If 'val'
311-
// is unknown or undefined, we lazily substitute '0'. Otherwise,
312-
// return 'val'.
313-
static inline SVal getValue(SVal val, SValBuilder &svalBuilder) {
314-
return val.isUndef() ? svalBuilder.makeZeroArrayIndex() : val;
315-
}
316-
317-
// Scale a base value by a scaling factor, and return the scaled
318-
// value as an SVal. Used by 'computeOffset'.
319-
static inline SVal scaleValue(ProgramStateRef state,
320-
NonLoc baseVal, CharUnits scaling,
321-
SValBuilder &sb) {
322-
return sb.evalBinOpNN(state, BO_Mul, baseVal,
323-
sb.makeArrayIndex(scaling.getQuantity()),
324-
sb.getArrayIndexType());
325-
}
326-
327-
// Add an SVal to another, treating unknown and undefined values as
328-
// summing to UnknownVal. Used by 'computeOffset'.
329-
static SVal addValue(ProgramStateRef state, SVal x, SVal y,
330-
SValBuilder &svalBuilder) {
331-
// We treat UnknownVals and UndefinedVals the same here because we
332-
// only care about computing offsets.
333-
if (x.isUnknownOrUndef() || y.isUnknownOrUndef())
334-
return UnknownVal();
335-
336-
return svalBuilder.evalBinOpNN(state, BO_Add, x.castAs<NonLoc>(),
337-
y.castAs<NonLoc>(),
338-
svalBuilder.getArrayIndexType());
339-
}
340-
341-
/// Compute a raw byte offset from a base region. Used for array bounds
342-
/// checking.
343-
RegionRawOffsetV2 RegionRawOffsetV2::computeOffset(ProgramStateRef state,
344-
SValBuilder &svalBuilder,
345-
SVal location)
346-
{
347-
const MemRegion *region = location.getAsRegion();
348-
SVal offset = UndefinedVal();
349-
350-
while (region) {
351-
switch (region->getKind()) {
352-
default: {
353-
if (const SubRegion *subReg = dyn_cast<SubRegion>(region)) {
354-
if (auto Offset = getValue(offset, svalBuilder).getAs<NonLoc>())
355-
return RegionRawOffsetV2(subReg, *Offset);
356-
}
357-
return RegionRawOffsetV2();
358-
}
359-
case MemRegion::ElementRegionKind: {
360-
const ElementRegion *elemReg = cast<ElementRegion>(region);
361-
SVal index = elemReg->getIndex();
362-
if (!isa<NonLoc>(index))
363-
return RegionRawOffsetV2();
364-
QualType elemType = elemReg->getElementType();
306+
/// For a given Location that can be represented as a symbolic expression
307+
/// Arr[Idx] (or perhaps Arr[Idx1][Idx2] etc.), return the parent memory block
308+
/// Arr and the distance of Location from the beginning of Arr (expressed in a
309+
/// NonLoc that specifies the number of CharUnits). Returns nullopt when these
310+
/// cannot be determined.
311+
std::optional<RegionRawOffsetV2>
312+
RegionRawOffsetV2::computeOffset(ProgramStateRef State, SValBuilder &SVB,
313+
SVal Location) {
314+
QualType T = SVB.getArrayIndexType();
315+
auto Calc = [&SVB, State, T](BinaryOperatorKind Op, NonLoc LHS, NonLoc RHS) {
316+
// We will use this utility to add and multiply values.
317+
return SVB.evalBinOpNN(State, Op, LHS, RHS, T).getAs<NonLoc>();
318+
};
319+
320+
const MemRegion *Region = Location.getAsRegion();
321+
NonLoc Offset = SVB.makeZeroArrayIndex();
322+
323+
while (Region) {
324+
if (const auto *ERegion = dyn_cast<ElementRegion>(Region)) {
325+
if (const auto Index = ERegion->getIndex().getAs<NonLoc>()) {
326+
QualType ElemType = ERegion->getElementType();
365327
// If the element is an incomplete type, go no further.
366-
ASTContext &astContext = svalBuilder.getContext();
367-
if (elemType->isIncompleteType())
368-
return RegionRawOffsetV2();
369-
370-
// Update the offset.
371-
offset = addValue(state,
372-
getValue(offset, svalBuilder),
373-
scaleValue(state,
374-
index.castAs<NonLoc>(),
375-
astContext.getTypeSizeInChars(elemType),
376-
svalBuilder),
377-
svalBuilder);
378-
379-
if (offset.isUnknownOrUndef())
380-
return RegionRawOffsetV2();
381-
382-
region = elemReg->getSuperRegion();
383-
continue;
328+
if (ElemType->isIncompleteType())
329+
return std::nullopt;
330+
331+
// Perform Offset += Index * sizeof(ElemType); then continue the offset
332+
// calculations with SuperRegion:
333+
NonLoc Size = SVB.makeArrayIndex(
334+
SVB.getContext().getTypeSizeInChars(ElemType).getQuantity());
335+
if (auto Delta = Calc(BO_Mul, *Index, Size)) {
336+
if (auto NewOffset = Calc(BO_Add, Offset, *Delta)) {
337+
Offset = *NewOffset;
338+
Region = ERegion->getSuperRegion();
339+
continue;
340+
}
341+
}
384342
}
343+
} else if (const auto *SRegion = dyn_cast<SubRegion>(Region)) {
344+
// NOTE: The dyn_cast<>() is expected to succeed, it'd be very surprising
345+
// to see a MemSpaceRegion at this point.
346+
// FIXME: We may return with {<Region>, 0} even if we didn't handle any
347+
// ElementRegion layers. I think that this behavior was introduced
348+
// accidentally by 8a4c760c204546aba566e302f299f7ed2e00e287 in 2011, so
349+
// it may be useful to review it in the future.
350+
return RegionRawOffsetV2(SRegion, Offset);
385351
}
352+
return std::nullopt;
386353
}
387-
return RegionRawOffsetV2();
354+
return std::nullopt;
388355
}
389356

390357
void ento::registerArrayBoundCheckerV2(CheckerManager &mgr) {

0 commit comments

Comments
 (0)