Skip to content

Commit 67f387c

Browse files
authored
[analyzer] Let the checkers query upper and lower bounds on symbols (#74141)
This commit extends the class `SValBuilder` with the methods `getMinValue()` and `getMaxValue()` to that work like `SValBuilder::getKnownValue()` but return the minimal/maximal possible value the `SVal` is not perfectly constrained. This extension of the ConstraintManager API is discussed at: https://discourse.llvm.org/t/expose-the-inferred-range-information-in-warning-messages/75192 As a simple proof-of-concept application of this new API, this commit extends a message from `core.BitwiseShift` with some range information that reports the assumptions of the analyzer. My main motivation for adding these methods is that I'll also want to use them in `ArrayBoundCheckerV2` to make the error messages less awkward, but I'm starting with this simpler and less important usecase because I want to avoid merge conflicts with my other commit #72107 which is currently under review. The testcase `too_large_right_operand_compound()` shows a situation where querying the range information does not work (and the extra information is not added to the error message). This also affects the debug utility `clang_analyzer_value()`, so the problem isn't in the fresh code. I'll do some investigations to resolve this, but I think that this commit is a step forward even with this limitation.
1 parent e3a97df commit 67f387c

File tree

6 files changed

+118
-14
lines changed

6 files changed

+118
-14
lines changed

clang/include/clang/StaticAnalyzer/Core/PathSensitive/ConstraintManager.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,22 @@ class ConstraintManager {
110110
return nullptr;
111111
}
112112

113+
/// Attempt to return the minimal possible value for a given symbol. Note
114+
/// that a ConstraintManager is not obligated to return a lower bound, it may
115+
/// also return nullptr.
116+
virtual const llvm::APSInt *getSymMinVal(ProgramStateRef state,
117+
SymbolRef sym) const {
118+
return nullptr;
119+
}
120+
121+
/// Attempt to return the minimal possible value for a given symbol. Note
122+
/// that a ConstraintManager is not obligated to return a lower bound, it may
123+
/// also return nullptr.
124+
virtual const llvm::APSInt *getSymMaxVal(ProgramStateRef state,
125+
SymbolRef sym) const {
126+
return nullptr;
127+
}
128+
113129
/// Scan all symbols referenced by the constraints. If the symbol is not
114130
/// alive, remove it.
115131
virtual ProgramStateRef removeDeadBindings(ProgramStateRef state,

clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ class SValBuilder {
110110
/// that value is returned. Otherwise, returns NULL.
111111
virtual const llvm::APSInt *getKnownValue(ProgramStateRef state, SVal val) = 0;
112112

113+
/// Tries to get the minimal possible (integer) value of a given SVal. If the
114+
/// constraint manager cannot provide an useful answer, this returns NULL.
115+
virtual const llvm::APSInt *getMinValue(ProgramStateRef state, SVal val) = 0;
116+
117+
/// Tries to get the maximal possible (integer) value of a given SVal. If the
118+
/// constraint manager cannot provide an useful answer, this returns NULL.
119+
virtual const llvm::APSInt *getMaxValue(ProgramStateRef state, SVal val) = 0;
120+
113121
/// Simplify symbolic expressions within a given SVal. Return an SVal
114122
/// that represents the same value, but is hopefully easier to work with
115123
/// than the original SVal.

clang/lib/StaticAnalyzer/Checkers/BitwiseShiftChecker.cpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,18 +172,24 @@ BugReportPtr BitwiseShiftValidator::checkOvershift() {
172172

173173
const SVal Right = Ctx.getSVal(operandExpr(OperandSide::Right));
174174

175-
std::string RightOpStr = "";
175+
std::string RightOpStr = "", LowerBoundStr = "";
176176
if (auto ConcreteRight = Right.getAs<nonloc::ConcreteInt>())
177177
RightOpStr = formatv(" '{0}'", ConcreteRight->getValue());
178+
else {
179+
SValBuilder &SVB = Ctx.getSValBuilder();
180+
if (const llvm::APSInt *MinRight = SVB.getMinValue(FoldedState, Right)) {
181+
LowerBoundStr = formatv(" >= {0},", MinRight->getExtValue());
182+
}
183+
}
178184

179185
std::string ShortMsg = formatv(
180186
"{0} shift{1}{2} overflows the capacity of '{3}'",
181187
isLeftShift() ? "Left" : "Right", RightOpStr.empty() ? "" : " by",
182188
RightOpStr, LHSTy.getAsString());
183-
std::string Msg =
184-
formatv("The result of {0} shift is undefined because the right "
185-
"operand{1} is not smaller than {2}, the capacity of '{3}'",
186-
shiftDir(), RightOpStr, LHSBitWidth, LHSTy.getAsString());
189+
std::string Msg = formatv(
190+
"The result of {0} shift is undefined because the right "
191+
"operand{1} is{2} not smaller than {3}, the capacity of '{4}'",
192+
shiftDir(), RightOpStr, LowerBoundStr, LHSBitWidth, LHSTy.getAsString());
187193
return createBugReport(ShortMsg, Msg);
188194
}
189195

clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1876,6 +1876,12 @@ class RangeConstraintManager : public RangedConstraintManager {
18761876
const llvm::APSInt *getSymVal(ProgramStateRef State,
18771877
SymbolRef Sym) const override;
18781878

1879+
const llvm::APSInt *getSymMinVal(ProgramStateRef State,
1880+
SymbolRef Sym) const override;
1881+
1882+
const llvm::APSInt *getSymMaxVal(ProgramStateRef State,
1883+
SymbolRef Sym) const override;
1884+
18791885
ProgramStateRef removeDeadBindings(ProgramStateRef State,
18801886
SymbolReaper &SymReaper) override;
18811887

@@ -2863,6 +2869,22 @@ const llvm::APSInt *RangeConstraintManager::getSymVal(ProgramStateRef St,
28632869
return T ? T->getConcreteValue() : nullptr;
28642870
}
28652871

2872+
const llvm::APSInt *RangeConstraintManager::getSymMinVal(ProgramStateRef St,
2873+
SymbolRef Sym) const {
2874+
const RangeSet *T = getConstraint(St, Sym);
2875+
if (!T || T->isEmpty())
2876+
return nullptr;
2877+
return &T->getMinValue();
2878+
}
2879+
2880+
const llvm::APSInt *RangeConstraintManager::getSymMaxVal(ProgramStateRef St,
2881+
SymbolRef Sym) const {
2882+
const RangeSet *T = getConstraint(St, Sym);
2883+
if (!T || T->isEmpty())
2884+
return nullptr;
2885+
return &T->getMaxValue();
2886+
}
2887+
28662888
//===----------------------------------------------------------------------===//
28672889
// Remove dead symbols from existing constraints
28682890
//===----------------------------------------------------------------------===//

clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ class SimpleSValBuilder : public SValBuilder {
2828
// returns NULL.
2929
// This is an implementation detail. Checkers should use `getKnownValue()`
3030
// instead.
31-
const llvm::APSInt *getConstValue(ProgramStateRef state, SVal V);
31+
static const llvm::APSInt *getConstValue(ProgramStateRef state, SVal V);
32+
33+
// Helper function that returns the value stored in a nonloc::ConcreteInt or
34+
// loc::ConcreteInt.
35+
static const llvm::APSInt *getConcreteValue(SVal V);
3236

3337
// With one `simplifySValOnce` call, a compound symbols might collapse to
3438
// simpler symbol tree that is still possible to further simplify. Thus, we
@@ -76,6 +80,16 @@ class SimpleSValBuilder : public SValBuilder {
7680
/// (integer) value, that value is returned. Otherwise, returns NULL.
7781
const llvm::APSInt *getKnownValue(ProgramStateRef state, SVal V) override;
7882

83+
/// Evaluates a given SVal by recursively evaluating and simplifying the
84+
/// children SVals, then returns its minimal possible (integer) value. If the
85+
/// constraint manager cannot provide a meaningful answer, this returns NULL.
86+
const llvm::APSInt *getMinValue(ProgramStateRef state, SVal V) override;
87+
88+
/// Evaluates a given SVal by recursively evaluating and simplifying the
89+
/// children SVals, then returns its maximal possible (integer) value. If the
90+
/// constraint manager cannot provide a meaningful answer, this returns NULL.
91+
const llvm::APSInt *getMaxValue(ProgramStateRef state, SVal V) override;
92+
7993
SVal simplifySVal(ProgramStateRef State, SVal V) override;
8094

8195
SVal MakeSymIntVal(const SymExpr *LHS, BinaryOperator::Opcode op,
@@ -1182,18 +1196,22 @@ SVal SimpleSValBuilder::evalBinOpLN(ProgramStateRef state,
11821196

11831197
const llvm::APSInt *SimpleSValBuilder::getConstValue(ProgramStateRef state,
11841198
SVal V) {
1185-
if (V.isUnknownOrUndef())
1186-
return nullptr;
1199+
if (const llvm::APSInt *Res = getConcreteValue(V))
1200+
return Res;
11871201

1202+
if (SymbolRef Sym = V.getAsSymbol())
1203+
return state->getConstraintManager().getSymVal(state, Sym);
1204+
1205+
return nullptr;
1206+
}
1207+
1208+
const llvm::APSInt *SimpleSValBuilder::getConcreteValue(SVal V) {
11881209
if (std::optional<loc::ConcreteInt> X = V.getAs<loc::ConcreteInt>())
11891210
return &X->getValue();
11901211

11911212
if (std::optional<nonloc::ConcreteInt> X = V.getAs<nonloc::ConcreteInt>())
11921213
return &X->getValue();
11931214

1194-
if (SymbolRef Sym = V.getAsSymbol())
1195-
return state->getConstraintManager().getSymVal(state, Sym);
1196-
11971215
return nullptr;
11981216
}
11991217

@@ -1202,6 +1220,32 @@ const llvm::APSInt *SimpleSValBuilder::getKnownValue(ProgramStateRef state,
12021220
return getConstValue(state, simplifySVal(state, V));
12031221
}
12041222

1223+
const llvm::APSInt *SimpleSValBuilder::getMinValue(ProgramStateRef state,
1224+
SVal V) {
1225+
V = simplifySVal(state, V);
1226+
1227+
if (const llvm::APSInt *Res = getConcreteValue(V))
1228+
return Res;
1229+
1230+
if (SymbolRef Sym = V.getAsSymbol())
1231+
return state->getConstraintManager().getSymMinVal(state, Sym);
1232+
1233+
return nullptr;
1234+
}
1235+
1236+
const llvm::APSInt *SimpleSValBuilder::getMaxValue(ProgramStateRef state,
1237+
SVal V) {
1238+
V = simplifySVal(state, V);
1239+
1240+
if (const llvm::APSInt *Res = getConcreteValue(V))
1241+
return Res;
1242+
1243+
if (SymbolRef Sym = V.getAsSymbol())
1244+
return state->getConstraintManager().getSymMaxVal(state, Sym);
1245+
1246+
return nullptr;
1247+
}
1248+
12051249
SVal SimpleSValBuilder::simplifyUntilFixpoint(ProgramStateRef State, SVal Val) {
12061250
SVal SimplifiedVal = simplifySValOnce(State, Val);
12071251
while (SimplifiedVal != Val) {

clang/test/Analysis/bitwise-shift-common.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// RUN: %clang_analyze_cc1 -analyzer-checker=core.BitwiseShift \
2+
// RUN: -analyzer-checker=debug.ExprInspection \
23
// RUN: -analyzer-output=text -verify \
34
// RUN: -triple x86_64-pc-linux-gnu -x c %s \
45
// RUN: -Wno-shift-count-negative -Wno-shift-negative-value \
56
// RUN: -Wno-shift-count-overflow -Wno-shift-overflow \
67
// RUN: -Wno-shift-sign-overflow
78
//
89
// RUN: %clang_analyze_cc1 -analyzer-checker=core.BitwiseShift \
10+
// RUN: -analyzer-checker=debug.ExprInspection \
911
// RUN: -analyzer-config core.BitwiseShift:Pedantic=true \
1012
// RUN: -analyzer-output=text -verify \
1113
// RUN: -triple x86_64-pc-linux-gnu -x c++ -std=c++20 %s \
@@ -85,17 +87,23 @@ int too_large_right_operand_symbolic(int left, int right) {
8587
return 0;
8688
return left >> right;
8789
// expected-warning@-1 {{Right shift overflows the capacity of 'int'}}
88-
// expected-note@-2 {{The result of right shift is undefined because the right operand is not smaller than 32, the capacity of 'int'}}
89-
// NOTE: the messages of the checker are a bit vague in this case, but the
90-
// tracking of the variables reveals our knowledge about them.
90+
// expected-note@-2 {{The result of right shift is undefined because the right operand is >= 32, not smaller than 32, the capacity of 'int'}}
9191
}
9292

93+
void clang_analyzer_value(int);
9394
int too_large_right_operand_compound(unsigned short arg) {
9495
// Note: this would be valid code with an 'unsigned int' because
9596
// unsigned addition is allowed to overflow.
97+
clang_analyzer_value(32+arg);
98+
// expected-warning@-1 {{32s:{ [-2147483648, 2147483647] }}
99+
// expected-note@-2 {{32s:{ [-2147483648, 2147483647] }}
96100
return 1 << (32 + arg);
97101
// expected-warning@-1 {{Left shift overflows the capacity of 'int'}}
98102
// expected-note@-2 {{The result of left shift is undefined because the right operand is not smaller than 32, the capacity of 'int'}}
103+
// FIXME: this message should be
104+
// {{The result of left shift is undefined because the right operand is >= 32, not smaller than 32, the capacity of 'int'}}
105+
// but for some reason neither the new logic, nor debug.ExprInspection and
106+
// clang_analyzer_value reports this range information.
99107
}
100108

101109
// TEST STATE UPDATES

0 commit comments

Comments
 (0)