1 //===-- MismatchedIteratorChecker.cpp -----------------------------*- C++ -*--//
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
7 //===----------------------------------------------------------------------===//
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.
13 //===----------------------------------------------------------------------===//
15 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
16 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
17 #include "clang/StaticAnalyzer/Core/Checker.h"
18 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
19 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
24 using namespace clang
;
26 using namespace iterator
;
30 class MismatchedIteratorChecker
31 : public Checker
<check::PreCall
, check::PreStmt
<BinaryOperator
>> {
33 std::unique_ptr
<BugType
> MismatchedBugType
;
35 void verifyMatch(CheckerContext
&C
, const SVal
&Iter
,
36 const MemRegion
*Cont
) const;
37 void verifyMatch(CheckerContext
&C
, const SVal
&Iter1
,
38 const SVal
&Iter2
) const;
39 void reportBug(const StringRef
&Message
, const SVal
&Val1
,
40 const SVal
&Val2
, CheckerContext
&C
,
41 ExplodedNode
*ErrNode
) const;
42 void reportBug(const StringRef
&Message
, const SVal
&Val
,
43 const MemRegion
*Reg
, CheckerContext
&C
,
44 ExplodedNode
*ErrNode
) const;
47 MismatchedIteratorChecker();
49 void checkPreCall(const CallEvent
&Call
, CheckerContext
&C
) const;
50 void checkPreStmt(const BinaryOperator
*BO
, CheckerContext
&C
) const;
56 MismatchedIteratorChecker::MismatchedIteratorChecker() {
57 MismatchedBugType
.reset(
58 new BugType(this, "Iterator(s) mismatched", "Misuse of STL APIs",
59 /*SuppressOnSink=*/true));
62 void MismatchedIteratorChecker::checkPreCall(const CallEvent
&Call
,
63 CheckerContext
&C
) const {
64 // Check for iterator mismatches
65 const auto *Func
= dyn_cast_or_null
<FunctionDecl
>(Call
.getDecl());
69 if (Func
->isOverloadedOperator() &&
70 isComparisonOperator(Func
->getOverloadedOperator())) {
71 // Check for comparisons of iterators of different containers
72 if (const auto *InstCall
= dyn_cast
<CXXInstanceCall
>(&Call
)) {
73 if (Call
.getNumArgs() < 1)
76 if (!isIteratorType(InstCall
->getCXXThisExpr()->getType()) ||
77 !isIteratorType(Call
.getArgExpr(0)->getType()))
80 verifyMatch(C
, InstCall
->getCXXThisVal(), Call
.getArgSVal(0));
82 if (Call
.getNumArgs() < 2)
85 if (!isIteratorType(Call
.getArgExpr(0)->getType()) ||
86 !isIteratorType(Call
.getArgExpr(1)->getType()))
89 verifyMatch(C
, Call
.getArgSVal(0), Call
.getArgSVal(1));
91 } else if (const auto *InstCall
= dyn_cast
<CXXInstanceCall
>(&Call
)) {
92 const auto *ContReg
= InstCall
->getCXXThisVal().getAsRegion();
95 // Check for erase, insert and emplace using iterator of another container
96 if (isEraseCall(Func
) || isEraseAfterCall(Func
)) {
97 verifyMatch(C
, Call
.getArgSVal(0),
98 InstCall
->getCXXThisVal().getAsRegion());
99 if (Call
.getNumArgs() == 2) {
100 verifyMatch(C
, Call
.getArgSVal(1),
101 InstCall
->getCXXThisVal().getAsRegion());
103 } else if (isInsertCall(Func
)) {
104 verifyMatch(C
, Call
.getArgSVal(0),
105 InstCall
->getCXXThisVal().getAsRegion());
106 if (Call
.getNumArgs() == 3 &&
107 isIteratorType(Call
.getArgExpr(1)->getType()) &&
108 isIteratorType(Call
.getArgExpr(2)->getType())) {
109 verifyMatch(C
, Call
.getArgSVal(1), Call
.getArgSVal(2));
111 } else if (isEmplaceCall(Func
)) {
112 verifyMatch(C
, Call
.getArgSVal(0),
113 InstCall
->getCXXThisVal().getAsRegion());
115 } else if (isa
<CXXConstructorCall
>(&Call
)) {
116 // Check match of first-last iterator pair in a constructor of a container
117 if (Call
.getNumArgs() < 2)
120 const auto *Ctr
= cast
<CXXConstructorDecl
>(Call
.getDecl());
121 if (Ctr
->getNumParams() < 2)
124 if (Ctr
->getParamDecl(0)->getName() != "first" ||
125 Ctr
->getParamDecl(1)->getName() != "last")
128 if (!isIteratorType(Call
.getArgExpr(0)->getType()) ||
129 !isIteratorType(Call
.getArgExpr(1)->getType()))
132 verifyMatch(C
, Call
.getArgSVal(0), Call
.getArgSVal(1));
134 // The main purpose of iterators is to abstract away from different
135 // containers and provide a (maybe limited) uniform access to them.
136 // This implies that any correctly written template function that
137 // works on multiple containers using iterators takes different
138 // template parameters for different containers. So we can safely
139 // assume that passing iterators of different containers as arguments
140 // whose type replaces the same template parameter is a bug.
143 // template<typename I1, typename I2>
144 // void f(I1 first1, I1 last1, I2 first2, I2 last2);
146 // In this case the first two arguments to f() must be iterators must belong
147 // to the same container and the last to also to the same container but
148 // not necessarily to the same as the first two.
150 const auto *Templ
= Func
->getPrimaryTemplate();
154 const auto *TParams
= Templ
->getTemplateParameters();
155 const auto *TArgs
= Func
->getTemplateSpecializationArgs();
157 // Iterate over all the template parameters
158 for (size_t I
= 0; I
< TParams
->size(); ++I
) {
159 const auto *TPDecl
= dyn_cast
<TemplateTypeParmDecl
>(TParams
->getParam(I
));
163 if (TPDecl
->isParameterPack())
166 const auto TAType
= TArgs
->get(I
).getAsType();
167 if (!isIteratorType(TAType
))
170 SVal LHS
= UndefinedVal();
172 // For every template parameter which is an iterator type in the
173 // instantiation look for all functions' parameters' type by it and
174 // check whether they belong to the same container
175 for (auto J
= 0U; J
< Func
->getNumParams(); ++J
) {
176 const auto *Param
= Func
->getParamDecl(J
);
177 const auto *ParamType
=
178 Param
->getType()->getAs
<SubstTemplateTypeParmType
>();
181 const TemplateTypeParmDecl
*D
= ParamType
->getReplacedParameter();
185 LHS
= Call
.getArgSVal(J
);
187 verifyMatch(C
, LHS
, Call
.getArgSVal(J
));
194 void MismatchedIteratorChecker::checkPreStmt(const BinaryOperator
*BO
,
195 CheckerContext
&C
) const {
196 if (!BO
->isComparisonOp())
199 ProgramStateRef State
= C
.getState();
200 SVal LVal
= State
->getSVal(BO
->getLHS(), C
.getLocationContext());
201 SVal RVal
= State
->getSVal(BO
->getRHS(), C
.getLocationContext());
202 verifyMatch(C
, LVal
, RVal
);
205 void MismatchedIteratorChecker::verifyMatch(CheckerContext
&C
, const SVal
&Iter
,
206 const MemRegion
*Cont
) const {
207 // Verify match between a container and the container of an iterator
208 Cont
= Cont
->getMostDerivedObjectRegion();
210 if (const auto *ContSym
= Cont
->getSymbolicBase()) {
211 if (isa
<SymbolConjured
>(ContSym
->getSymbol()))
215 auto State
= C
.getState();
216 const auto *Pos
= getIteratorPosition(State
, Iter
);
220 const auto *IterCont
= Pos
->getContainer();
222 // Skip symbolic regions based on conjured symbols. Two conjured symbols
223 // may or may not be the same. For example, the same function can return
224 // the same or a different container but we get different conjured symbols
225 // for each call. This may cause false positives so omit them from the check.
226 if (const auto *ContSym
= IterCont
->getSymbolicBase()) {
227 if (isa
<SymbolConjured
>(ContSym
->getSymbol()))
231 if (IterCont
!= Cont
) {
232 auto *N
= C
.generateNonFatalErrorNode(State
);
236 reportBug("Container accessed using foreign iterator argument.",
241 void MismatchedIteratorChecker::verifyMatch(CheckerContext
&C
,
243 const SVal
&Iter2
) const {
244 // Verify match between the containers of two iterators
245 auto State
= C
.getState();
246 const auto *Pos1
= getIteratorPosition(State
, Iter1
);
250 const auto *IterCont1
= Pos1
->getContainer();
252 // Skip symbolic regions based on conjured symbols. Two conjured symbols
253 // may or may not be the same. For example, the same function can return
254 // the same or a different container but we get different conjured symbols
255 // for each call. This may cause false positives so omit them from the check.
256 if (const auto *ContSym
= IterCont1
->getSymbolicBase()) {
257 if (isa
<SymbolConjured
>(ContSym
->getSymbol()))
261 const auto *Pos2
= getIteratorPosition(State
, Iter2
);
265 const auto *IterCont2
= Pos2
->getContainer();
266 if (const auto *ContSym
= IterCont2
->getSymbolicBase()) {
267 if (isa
<SymbolConjured
>(ContSym
->getSymbol()))
271 if (IterCont1
!= IterCont2
) {
272 auto *N
= C
.generateNonFatalErrorNode(State
);
275 reportBug("Iterators of different containers used where the "
276 "same container is expected.", Iter1
, Iter2
, C
, N
);
280 void MismatchedIteratorChecker::reportBug(const StringRef
&Message
,
284 ExplodedNode
*ErrNode
) const {
285 auto R
= std::make_unique
<PathSensitiveBugReport
>(*MismatchedBugType
, Message
,
287 R
->markInteresting(Val1
);
288 R
->markInteresting(Val2
);
289 C
.emitReport(std::move(R
));
292 void MismatchedIteratorChecker::reportBug(const StringRef
&Message
,
293 const SVal
&Val
, const MemRegion
*Reg
,
295 ExplodedNode
*ErrNode
) const {
296 auto R
= std::make_unique
<PathSensitiveBugReport
>(*MismatchedBugType
, Message
,
298 R
->markInteresting(Val
);
299 R
->markInteresting(Reg
);
300 C
.emitReport(std::move(R
));
303 void ento::registerMismatchedIteratorChecker(CheckerManager
&mgr
) {
304 mgr
.registerChecker
<MismatchedIteratorChecker
>();
307 bool ento::shouldRegisterMismatchedIteratorChecker(const CheckerManager
&mgr
) {