clang 22.0.0git
MismatchedIteratorChecker.cpp
Go to the documentation of this file.
1//===-- MismatchedIteratorChecker.cpp -----------------------------*- C++ -*--//
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// Defines a checker for mistakenly applying a foreign iterator on a container
10// and for using iterators of two different containers in a context where
11// iterators of the same container should be used.
12//
13//===----------------------------------------------------------------------===//
14
20
21
22#include "Iterator.h"
23
24using namespace clang;
25using namespace ento;
26using namespace iterator;
27
28namespace {
29
30class MismatchedIteratorChecker
31 : public Checker<check::PreCall, check::PreStmt<BinaryOperator>> {
32
33 const BugType MismatchedBugType{this, "Iterator(s) mismatched",
34 "Misuse of STL APIs",
35 /*SuppressOnSink=*/true};
36
37 void verifyMatch(CheckerContext &C, SVal Iter, const MemRegion *Cont) const;
38 void verifyMatch(CheckerContext &C, SVal Iter1, SVal Iter2) const;
39 void reportBug(StringRef Message, SVal Val1, SVal Val2, CheckerContext &C,
40 ExplodedNode *ErrNode) const;
41 void reportBug(StringRef Message, SVal Val, const MemRegion *Reg,
42 CheckerContext &C, ExplodedNode *ErrNode) const;
43
44public:
45 void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
46 void checkPreStmt(const BinaryOperator *BO, CheckerContext &C) const;
47
48};
49
50} // namespace
51
52void MismatchedIteratorChecker::checkPreCall(const CallEvent &Call,
53 CheckerContext &C) const {
54 // Check for iterator mismatches
55 const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
56 if (!Func)
57 return;
58
59 if (Func->isOverloadedOperator() &&
60 isComparisonOperator(Func->getOverloadedOperator())) {
61 // Check for comparisons of iterators of different containers
62 if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
63 if (Call.getNumArgs() < 1)
64 return;
65
66 if (!isIteratorType(InstCall->getCXXThisExpr()->getType()) ||
67 !isIteratorType(Call.getArgExpr(0)->getType()))
68 return;
69
70 verifyMatch(C, InstCall->getCXXThisVal(), Call.getArgSVal(0));
71 } else {
72 if (Call.getNumArgs() < 2)
73 return;
74
75 if (!isIteratorType(Call.getArgExpr(0)->getType()) ||
76 !isIteratorType(Call.getArgExpr(1)->getType()))
77 return;
78
79 verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1));
80 }
81 } else if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
82 const auto *ContReg = InstCall->getCXXThisVal().getAsRegion();
83 if (!ContReg)
84 return;
85 // Check for erase, insert and emplace using iterator of another container
87 verifyMatch(C, Call.getArgSVal(0),
88 InstCall->getCXXThisVal().getAsRegion());
89 if (Call.getNumArgs() == 2) {
90 verifyMatch(C, Call.getArgSVal(1),
91 InstCall->getCXXThisVal().getAsRegion());
92 }
93 } else if (isInsertCall(Func)) {
94 if (Call.getNumArgs() == 2 &&
95 isIteratorType(Call.getArgExpr(0)->getType()) &&
96 isIteratorType(Call.getArgExpr(1)->getType())) {
97 verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1));
98 } else {
99 verifyMatch(C, Call.getArgSVal(0),
100 InstCall->getCXXThisVal().getAsRegion());
101 if (Call.getNumArgs() == 3 &&
102 isIteratorType(Call.getArgExpr(1)->getType()) &&
103 isIteratorType(Call.getArgExpr(2)->getType())) {
104 verifyMatch(C, Call.getArgSVal(1), Call.getArgSVal(2));
105 }
106 }
107 } else if (isEmplaceCall(Func)) {
108 verifyMatch(C, Call.getArgSVal(0),
109 InstCall->getCXXThisVal().getAsRegion());
110 }
111 } else if (isa<CXXConstructorCall>(&Call)) {
112 // Check match of first-last iterator pair in a constructor of a container
113 if (Call.getNumArgs() < 2)
114 return;
115
116 const auto *Ctr = cast<CXXConstructorDecl>(Call.getDecl());
117 if (Ctr->getNumParams() < 2)
118 return;
119
120 if (Ctr->getParamDecl(0)->getName() != "first" ||
121 Ctr->getParamDecl(1)->getName() != "last")
122 return;
123
124 if (!isIteratorType(Call.getArgExpr(0)->getType()) ||
125 !isIteratorType(Call.getArgExpr(1)->getType()))
126 return;
127
128 verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1));
129 } else {
130 // The main purpose of iterators is to abstract away from different
131 // containers and provide a (maybe limited) uniform access to them.
132 // This implies that any correctly written template function that
133 // works on multiple containers using iterators takes different
134 // template parameters for different containers. So we can safely
135 // assume that passing iterators of different containers as arguments
136 // whose type replaces the same template parameter is a bug.
137 //
138 // Example:
139 // template<typename I1, typename I2>
140 // void f(I1 first1, I1 last1, I2 first2, I2 last2);
141 //
142 // In this case the first two arguments to f() must be iterators must belong
143 // to the same container and the last to also to the same container but
144 // not necessarily to the same as the first two.
145
146 const auto *Templ = Func->getPrimaryTemplate();
147 if (!Templ)
148 return;
149
150 const auto *TParams = Templ->getTemplateParameters();
151 const auto *TArgs = Func->getTemplateSpecializationArgs();
152
153 // Iterate over all the template parameters
154 for (size_t I = 0; I < TParams->size(); ++I) {
155 const auto *TPDecl = dyn_cast<TemplateTypeParmDecl>(TParams->getParam(I));
156 if (!TPDecl)
157 continue;
158
159 if (TPDecl->isParameterPack())
160 continue;
161
162 const auto TAType = TArgs->get(I).getAsType();
163 if (!isIteratorType(TAType))
164 continue;
165
166 SVal LHS = UndefinedVal();
167
168 // For every template parameter which is an iterator type in the
169 // instantiation look for all functions' parameters' type by it and
170 // check whether they belong to the same container
171 for (auto J = 0U; J < Func->getNumParams(); ++J) {
172 const auto *Param = Func->getParamDecl(J);
173 const auto *ParamType =
174 Param->getType()->getAs<SubstTemplateTypeParmType>();
175 if (!ParamType)
176 continue;
177 const TemplateTypeParmDecl *D = ParamType->getReplacedParameter();
178 if (D != TPDecl)
179 continue;
180 if (LHS.isUndef()) {
181 LHS = Call.getArgSVal(J);
182 } else {
183 verifyMatch(C, LHS, Call.getArgSVal(J));
184 }
185 }
186 }
187 }
188}
189
190void MismatchedIteratorChecker::checkPreStmt(const BinaryOperator *BO,
191 CheckerContext &C) const {
192 if (!BO->isComparisonOp())
193 return;
194
195 ProgramStateRef State = C.getState();
196 SVal LVal = State->getSVal(BO->getLHS(), C.getLocationContext());
197 SVal RVal = State->getSVal(BO->getRHS(), C.getLocationContext());
198 verifyMatch(C, LVal, RVal);
199}
200
201void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, SVal Iter,
202 const MemRegion *Cont) const {
203 // Verify match between a container and the container of an iterator
204 Cont = Cont->getMostDerivedObjectRegion();
205
206 if (const auto *ContSym = Cont->getSymbolicBase()) {
207 if (isa<SymbolConjured>(ContSym->getSymbol()))
208 return;
209 }
210
211 auto State = C.getState();
212 const auto *Pos = getIteratorPosition(State, Iter);
213 if (!Pos)
214 return;
215
216 const auto *IterCont = Pos->getContainer();
217
218 // Skip symbolic regions based on conjured symbols. Two conjured symbols
219 // may or may not be the same. For example, the same function can return
220 // the same or a different container but we get different conjured symbols
221 // for each call. This may cause false positives so omit them from the check.
222 if (const auto *ContSym = IterCont->getSymbolicBase()) {
223 if (isa<SymbolConjured>(ContSym->getSymbol()))
224 return;
225 }
226
227 if (IterCont != Cont) {
228 auto *N = C.generateNonFatalErrorNode(State);
229 if (!N) {
230 return;
231 }
232 reportBug("Container accessed using foreign iterator argument.",
233 Iter, Cont, C, N);
234 }
235}
236
237void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, SVal Iter1,
238 SVal Iter2) const {
239 // Verify match between the containers of two iterators
240 auto State = C.getState();
241 const auto *Pos1 = getIteratorPosition(State, Iter1);
242 if (!Pos1)
243 return;
244
245 const auto *IterCont1 = Pos1->getContainer();
246
247 // Skip symbolic regions based on conjured symbols. Two conjured symbols
248 // may or may not be the same. For example, the same function can return
249 // the same or a different container but we get different conjured symbols
250 // for each call. This may cause false positives so omit them from the check.
251 if (const auto *ContSym = IterCont1->getSymbolicBase()) {
252 if (isa<SymbolConjured>(ContSym->getSymbol()))
253 return;
254 }
255
256 const auto *Pos2 = getIteratorPosition(State, Iter2);
257 if (!Pos2)
258 return;
259
260 const auto *IterCont2 = Pos2->getContainer();
261 if (const auto *ContSym = IterCont2->getSymbolicBase()) {
262 if (isa<SymbolConjured>(ContSym->getSymbol()))
263 return;
264 }
265
266 if (IterCont1 != IterCont2) {
267 auto *N = C.generateNonFatalErrorNode(State);
268 if (!N)
269 return;
270 reportBug("Iterators of different containers used where the "
271 "same container is expected.", Iter1, Iter2, C, N);
272 }
273}
274
275void MismatchedIteratorChecker::reportBug(StringRef Message, SVal Val1,
276 SVal Val2, CheckerContext &C,
277 ExplodedNode *ErrNode) const {
278 auto R = std::make_unique<PathSensitiveBugReport>(MismatchedBugType, Message,
279 ErrNode);
280 R->markInteresting(Val1);
281 R->markInteresting(Val2);
282 C.emitReport(std::move(R));
283}
284
285void MismatchedIteratorChecker::reportBug(StringRef Message, SVal Val,
286 const MemRegion *Reg,
287 CheckerContext &C,
288 ExplodedNode *ErrNode) const {
289 auto R = std::make_unique<PathSensitiveBugReport>(MismatchedBugType, Message,
290 ErrNode);
291 R->markInteresting(Val);
292 R->markInteresting(Reg);
293 C.emitReport(std::move(R));
294}
295
296void ento::registerMismatchedIteratorChecker(CheckerManager &mgr) {
297 mgr.registerChecker<MismatchedIteratorChecker>();
298}
299
300bool ento::shouldRegisterMismatchedIteratorChecker(const CheckerManager &mgr) {
301 return true;
302}
Expr * getLHS() const
Definition Expr.h:4022
static bool isComparisonOp(Opcode Opc)
Definition Expr.h:4072
Expr * getRHS() const
Definition Expr.h:4024
Represents an abstract call to a function or method along a particular path.
Definition CallEvent.h:153
CHECKER * registerChecker(AT &&...Args)
Register a single-part checker (derived from Checker): construct its singleton instance,...
Simple checker classes that implement one frontend (i.e.
Definition Checker.h:553
const SymbolicRegion * getSymbolicBase() const
If this is a symbolic region, returns the region.
LLVM_ATTRIBUTE_RETURNS_NONNULL const MemRegion * getMostDerivedObjectRegion() const
Recursively retrieve the region of the most derived class instance of regions of C++ base class insta...
bool isUndef() const
Definition SVals.h:107
bool isEraseCall(const FunctionDecl *Func)
Definition Iterator.cpp:98
const IteratorPosition * getIteratorPosition(ProgramStateRef State, SVal Val)
Definition Iterator.cpp:184
bool isInsertCall(const FunctionDecl *Func)
Definition Iterator.cpp:76
bool isIteratorType(const QualType &Type)
Definition Iterator.cpp:19
bool isEmplaceCall(const FunctionDecl *Func)
Definition Iterator.cpp:87
bool isEraseAfterCall(const FunctionDecl *Func)
Definition Iterator.cpp:112
bool isComparisonOperator(OverloadedOperatorKind OK)
Definition Iterator.cpp:71
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
The JSON file list parser is used to communicate input to InstallAPI.
bool isa(CodeGen::Address addr)
Definition Address.h:330
U cast(CodeGen::Address addr)
Definition Address.h:327