Skip to content

Commit c6b8484

Browse files
author
Gabor Marton
committed
[analyzer] StdLibraryFunctionsChecker refactor w/ inheritance
Summary: Currently, ValueRange is very hard to extend with new kind of constraints. For instance, it forcibly encapsulates relations between arguments and the return value (ComparesToArgument) besides handling the regular value ranges (OutOfRange, WithinRange). ValueRange in this form is not suitable to add new constraints on arguments like "not-null". This refactor introduces a new base class ValueConstraint with an abstract apply function. Descendants must override this. There are 2 descendants: RangeConstraint and ComparisonConstraint. In the following patches I am planning to add the NotNullConstraint, and additional virtual functions like `negate()` and `warning()`. Reviewers: NoQ, Szelethus, balazske, gamesh411, baloghadamsoftware, steakhal Subscribers: whisperity, xazax.hun, szepet, rnkovacs, a.sidorin, mikhail.ramalho, donat.nagy, dkrupp, Charusso, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D74973
1 parent 506406c commit c6b8484

File tree

1 file changed

+81
-72
lines changed

1 file changed

+81
-72
lines changed

clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp

Lines changed: 81 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,6 @@ class StdLibraryFunctionsChecker : public Checker<check::PostCall, eval::Call> {
7171
/// to us. If he doesn't, he performs additional invalidations.
7272
enum InvalidationKind { NoEvalCall, EvalCallAsPure };
7373

74-
/// A pair of ValueRangeKind and IntRangeVector would describe a range
75-
/// imposed on a particular argument or return value symbol.
76-
///
77-
/// Given a range, should the argument stay inside or outside this range?
78-
/// The special `ComparesToArgument' value indicates that we should
79-
/// impose a constraint that involves other argument or return value symbols.
80-
enum ValueRangeKind { OutOfRange, WithinRange, ComparesToArgument };
81-
8274
// The universal integral type to use in value range descriptions.
8375
// Unsigned to make sure overflows are well-defined.
8476
typedef uint64_t RangeInt;
@@ -93,75 +85,87 @@ class StdLibraryFunctionsChecker : public Checker<check::PostCall, eval::Call> {
9385
/// ArgNo in CallExpr and CallEvent is defined as Unsigned, but
9486
/// obviously uint32_t should be enough for all practical purposes.
9587
typedef uint32_t ArgNo;
96-
static const ArgNo Ret = std::numeric_limits<ArgNo>::max();
97-
98-
/// Incapsulates a single range on a single symbol within a branch.
99-
class ValueRange {
100-
ArgNo ArgN; // Argument to which we apply the range.
101-
ValueRangeKind Kind; // Kind of range definition.
102-
IntRangeVector Args; // Polymorphic arguments.
88+
static const ArgNo Ret;
10389

90+
/// Polymorphic base class that represents a constraint on a given argument
91+
/// (or return value) of a function. Derived classes implement different kind
92+
/// of constraints, e.g range constraints or correlation between two
93+
/// arguments.
94+
class ValueConstraint {
10495
public:
105-
ValueRange(ArgNo ArgN, ValueRangeKind Kind, const IntRangeVector &Args)
106-
: ArgN(ArgN), Kind(Kind), Args(Args) {}
107-
96+
ValueConstraint(ArgNo ArgN) : ArgN(ArgN) {}
97+
virtual ~ValueConstraint() {}
98+
/// Apply the effects of the constraint on the given program state. If null
99+
/// is returned then the constraint is not feasible.
100+
virtual ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call,
101+
const Summary &Summary) const = 0;
108102
ArgNo getArgNo() const { return ArgN; }
109-
ValueRangeKind getKind() const { return Kind; }
110-
111-
BinaryOperator::Opcode getOpcode() const {
112-
assert(Kind == ComparesToArgument);
113-
assert(Args.size() == 1);
114-
BinaryOperator::Opcode Op =
115-
static_cast<BinaryOperator::Opcode>(Args[0].first);
116-
assert(BinaryOperator::isComparisonOp(Op) &&
117-
"Only comparison ops are supported for ComparesToArgument");
118-
return Op;
119-
}
120103

121-
ArgNo getOtherArgNo() const {
122-
assert(Kind == ComparesToArgument);
123-
assert(Args.size() == 1);
124-
return static_cast<ArgNo>(Args[0].second);
125-
}
104+
protected:
105+
ArgNo ArgN; // Argument to which we apply the constraint.
106+
};
107+
108+
/// Given a range, should the argument stay inside or outside this range?
109+
enum RangeKind { OutOfRange, WithinRange };
110+
111+
/// Encapsulates a single range on a single symbol within a branch.
112+
class RangeConstraint : public ValueConstraint {
113+
RangeKind Kind; // Kind of range definition.
114+
IntRangeVector Args; // Polymorphic arguments.
115+
116+
public:
117+
RangeConstraint(ArgNo ArgN, RangeKind Kind, const IntRangeVector &Args)
118+
: ValueConstraint(ArgN), Kind(Kind), Args(Args) {}
126119

127120
const IntRangeVector &getRanges() const {
128-
assert(Kind != ComparesToArgument);
129121
return Args;
130122
}
131123

132-
// We avoid creating a virtual apply() method because
133-
// it makes initializer lists harder to write.
134124
private:
135125
ProgramStateRef applyAsOutOfRange(ProgramStateRef State,
136126
const CallEvent &Call,
137127
const Summary &Summary) const;
138128
ProgramStateRef applyAsWithinRange(ProgramStateRef State,
139129
const CallEvent &Call,
140130
const Summary &Summary) const;
141-
ProgramStateRef applyAsComparesToArgument(ProgramStateRef State,
142-
const CallEvent &Call,
143-
const Summary &Summary) const;
144-
145131
public:
146132
ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call,
147-
const Summary &Summary) const {
133+
const Summary &Summary) const override {
148134
switch (Kind) {
149135
case OutOfRange:
150136
return applyAsOutOfRange(State, Call, Summary);
151137
case WithinRange:
152138
return applyAsWithinRange(State, Call, Summary);
153-
case ComparesToArgument:
154-
return applyAsComparesToArgument(State, Call, Summary);
155139
}
156-
llvm_unreachable("Unknown ValueRange kind!");
140+
llvm_unreachable("Unknown range kind!");
157141
}
158142
};
159143

160-
/// The complete list of ranges that defines a single branch.
161-
typedef std::vector<ValueRange> ValueRangeSet;
144+
class ComparisonConstraint : public ValueConstraint {
145+
BinaryOperator::Opcode Opcode;
146+
ArgNo OtherArgN;
147+
148+
public:
149+
ComparisonConstraint(ArgNo ArgN, BinaryOperator::Opcode Opcode,
150+
ArgNo OtherArgN)
151+
: ValueConstraint(ArgN), Opcode(Opcode), OtherArgN(OtherArgN) {}
152+
ArgNo getOtherArgNo() const { return OtherArgN; }
153+
BinaryOperator::Opcode getOpcode() const { return Opcode; }
154+
ProgramStateRef apply(ProgramStateRef State, const CallEvent &Call,
155+
const Summary &Summary) const override;
156+
};
157+
158+
// Pointer to the ValueConstraint. We need a copyable, polymorphic and
159+
// default initialize able type (vector needs that). A raw pointer was good,
160+
// however, we cannot default initialize that. unique_ptr makes the Summary
161+
// class non-copyable, therefore not an option. Releasing the copyability
162+
// requirement would render the initialization of the Summary map infeasible.
163+
using ValueConstraintPtr = std::shared_ptr<ValueConstraint>;
164+
/// The complete list of constraints that defines a single branch.
165+
typedef std::vector<ValueConstraintPtr> ConstraintSet;
162166

163167
using ArgTypes = std::vector<QualType>;
164-
using Ranges = std::vector<ValueRangeSet>;
168+
using Cases = std::vector<ConstraintSet>;
165169

166170
/// Includes information about function prototype (which is necessary to
167171
/// ensure we're modeling the right function and casting values properly),
@@ -171,14 +175,14 @@ class StdLibraryFunctionsChecker : public Checker<check::PostCall, eval::Call> {
171175
const ArgTypes ArgTys;
172176
const QualType RetTy;
173177
const InvalidationKind InvalidationKd;
174-
Ranges Cases;
175-
ValueRangeSet ArgConstraints;
178+
Cases CaseConstraints;
179+
ConstraintSet ArgConstraints;
176180

177181
Summary(ArgTypes ArgTys, QualType RetTy, InvalidationKind InvalidationKd)
178182
: ArgTys(ArgTys), RetTy(RetTy), InvalidationKd(InvalidationKd) {}
179183

180-
Summary &Case(ValueRangeSet VRS) {
181-
Cases.push_back(VRS);
184+
Summary &Case(ConstraintSet&& CS) {
185+
CaseConstraints.push_back(std::move(CS));
182186
return *this;
183187
}
184188

@@ -244,9 +248,13 @@ class StdLibraryFunctionsChecker : public Checker<check::PostCall, eval::Call> {
244248

245249
void initFunctionSummaries(CheckerContext &C) const;
246250
};
251+
252+
const StdLibraryFunctionsChecker::ArgNo StdLibraryFunctionsChecker::Ret =
253+
std::numeric_limits<ArgNo>::max();
254+
247255
} // end of anonymous namespace
248256

249-
ProgramStateRef StdLibraryFunctionsChecker::ValueRange::applyAsOutOfRange(
257+
ProgramStateRef StdLibraryFunctionsChecker::RangeConstraint::applyAsOutOfRange(
250258
ProgramStateRef State, const CallEvent &Call,
251259
const Summary &Summary) const {
252260

@@ -273,7 +281,7 @@ ProgramStateRef StdLibraryFunctionsChecker::ValueRange::applyAsOutOfRange(
273281
return State;
274282
}
275283

276-
ProgramStateRef StdLibraryFunctionsChecker::ValueRange::applyAsWithinRange(
284+
ProgramStateRef StdLibraryFunctionsChecker::RangeConstraint::applyAsWithinRange(
277285
ProgramStateRef State, const CallEvent &Call,
278286
const Summary &Summary) const {
279287

@@ -330,8 +338,7 @@ ProgramStateRef StdLibraryFunctionsChecker::ValueRange::applyAsWithinRange(
330338
return State;
331339
}
332340

333-
ProgramStateRef
334-
StdLibraryFunctionsChecker::ValueRange::applyAsComparesToArgument(
341+
ProgramStateRef StdLibraryFunctionsChecker::ComparisonConstraint::apply(
335342
ProgramStateRef State, const CallEvent &Call,
336343
const Summary &Summary) const {
337344

@@ -367,15 +374,15 @@ void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call,
367374
if (!FoundSummary)
368375
return;
369376

370-
// Now apply ranges.
377+
// Now apply the constraints.
371378
const Summary &Summary = *FoundSummary;
372379
ProgramStateRef State = C.getState();
373380

374381
// Apply case/branch specifications.
375-
for (const auto &VRS : Summary.Cases) {
382+
for (const auto &VRS : Summary.CaseConstraints) {
376383
ProgramStateRef NewState = State;
377384
for (const auto &VR: VRS) {
378-
NewState = VR.apply(NewState, Call, Summary);
385+
NewState = VR->apply(NewState, Call, Summary);
379386
if (!NewState)
380387
break;
381388
}
@@ -555,24 +562,26 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
555562
// Please update the list of functions in the header after editing!
556563
//
557564

558-
// Below are helper functions to create the summaries.
559-
auto ArgumentCondition = [](ArgNo ArgN, ValueRangeKind Kind,
560-
IntRangeVector Ranges) -> ValueRange {
561-
ValueRange VR{ArgN, Kind, Ranges};
562-
return VR;
563-
};
564-
auto ReturnValueCondition = [](ValueRangeKind Kind,
565-
IntRangeVector Ranges) -> ValueRange {
566-
ValueRange VR{Ret, Kind, Ranges};
567-
return VR;
565+
// Below are helpers functions to create the summaries.
566+
auto ArgumentCondition = [](ArgNo ArgN, RangeKind Kind,
567+
IntRangeVector Ranges) {
568+
return std::make_shared<RangeConstraint>(ArgN, Kind, Ranges);
568569
};
570+
struct {
571+
auto operator()(RangeKind Kind, IntRangeVector Ranges) {
572+
return std::make_shared<RangeConstraint>(Ret, Kind, Ranges);
573+
}
574+
auto operator()(BinaryOperator::Opcode Op, ArgNo OtherArgN) {
575+
return std::make_shared<ComparisonConstraint>(Ret, Op, OtherArgN);
576+
}
577+
} ReturnValueCondition;
569578
auto Range = [](RangeInt b, RangeInt e) {
570579
return IntRangeVector{std::pair<RangeInt, RangeInt>{b, e}};
571580
};
572581
auto SingleValue = [](RangeInt v) {
573582
return IntRangeVector{std::pair<RangeInt, RangeInt>{v, v}};
574583
};
575-
auto IsLessThan = [](ArgNo ArgN) { return IntRangeVector{{BO_LE, ArgN}}; };
584+
auto LessThanOrEq = BO_LE;
576585

577586
using RetType = QualType;
578587

@@ -585,14 +594,14 @@ void StdLibraryFunctionsChecker::initFunctionSummaries(
585594
auto Read = [&](RetType R, RangeInt Max) {
586595
return Summary(ArgTypes{Irrelevant, Irrelevant, SizeTy}, RetType{R},
587596
NoEvalCall)
588-
.Case({ReturnValueCondition(ComparesToArgument, IsLessThan(2)),
597+
.Case({ReturnValueCondition(LessThanOrEq, ArgNo(2)),
589598
ReturnValueCondition(WithinRange, Range(-1, Max))});
590599
};
591600
auto Fread = [&]() {
592601
return Summary(ArgTypes{Irrelevant, Irrelevant, SizeTy, Irrelevant},
593602
RetType{SizeTy}, NoEvalCall)
594603
.Case({
595-
ReturnValueCondition(ComparesToArgument, IsLessThan(2)),
604+
ReturnValueCondition(LessThanOrEq, ArgNo(2)),
596605
});
597606
};
598607
auto Getline = [&](RetType R, RangeInt Max) {

0 commit comments

Comments
 (0)