xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp (revision 8ee3dfd74653e30f48dd9f49ba24f43547e6a549)
1afb13afcSAdam Balogh //===-- MismatchedIteratorChecker.cpp -----------------------------*- C++ -*--//
2afb13afcSAdam Balogh //
3afb13afcSAdam Balogh // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4afb13afcSAdam Balogh // See https://llvm.org/LICENSE.txt for license information.
5afb13afcSAdam Balogh // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6afb13afcSAdam Balogh //
7afb13afcSAdam Balogh //===----------------------------------------------------------------------===//
8afb13afcSAdam Balogh //
9afb13afcSAdam Balogh // Defines a checker for mistakenly applying a foreign iterator on a container
10afb13afcSAdam Balogh // and for using iterators of two different containers in a context where
11afb13afcSAdam Balogh // iterators of the same container should be used.
12afb13afcSAdam Balogh //
13afb13afcSAdam Balogh //===----------------------------------------------------------------------===//
14afb13afcSAdam Balogh 
15afb13afcSAdam Balogh #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
16afb13afcSAdam Balogh #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
17afb13afcSAdam Balogh #include "clang/StaticAnalyzer/Core/Checker.h"
18afb13afcSAdam Balogh #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
19afb13afcSAdam Balogh #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
20afb13afcSAdam Balogh 
21afb13afcSAdam Balogh 
22afb13afcSAdam Balogh #include "Iterator.h"
23afb13afcSAdam Balogh 
24afb13afcSAdam Balogh using namespace clang;
25afb13afcSAdam Balogh using namespace ento;
26afb13afcSAdam Balogh using namespace iterator;
27afb13afcSAdam Balogh 
28afb13afcSAdam Balogh namespace {
29afb13afcSAdam Balogh 
30afb13afcSAdam Balogh class MismatchedIteratorChecker
319e63b190SAdam Balogh   : public Checker<check::PreCall, check::PreStmt<BinaryOperator>> {
32afb13afcSAdam Balogh 
3318f219c5SBalazs Benics   const BugType MismatchedBugType{this, "Iterator(s) mismatched",
3418f219c5SBalazs Benics                                   "Misuse of STL APIs",
3518f219c5SBalazs Benics                                   /*SuppressOnSink=*/true};
36afb13afcSAdam Balogh 
37*8ee3dfd7SBalazs Benics   void verifyMatch(CheckerContext &C, SVal Iter, const MemRegion *Cont) const;
38*8ee3dfd7SBalazs Benics   void verifyMatch(CheckerContext &C, SVal Iter1, SVal Iter2) const;
39*8ee3dfd7SBalazs Benics   void reportBug(StringRef Message, SVal Val1, SVal Val2, CheckerContext &C,
40*8ee3dfd7SBalazs Benics                  ExplodedNode *ErrNode) const;
41*8ee3dfd7SBalazs Benics   void reportBug(StringRef Message, SVal Val, const MemRegion *Reg,
427619050cSBalazs Benics                  CheckerContext &C, ExplodedNode *ErrNode) const;
43afb13afcSAdam Balogh 
44afb13afcSAdam Balogh public:
45afb13afcSAdam Balogh   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
469e63b190SAdam Balogh   void checkPreStmt(const BinaryOperator *BO, CheckerContext &C) const;
47afb13afcSAdam Balogh 
48afb13afcSAdam Balogh };
49afb13afcSAdam Balogh 
50afb13afcSAdam Balogh } // namespace
51afb13afcSAdam Balogh 
checkPreCall(const CallEvent & Call,CheckerContext & C) const52afb13afcSAdam Balogh void MismatchedIteratorChecker::checkPreCall(const CallEvent &Call,
53afb13afcSAdam Balogh                                              CheckerContext &C) const {
54afb13afcSAdam Balogh   // Check for iterator mismatches
55afb13afcSAdam Balogh   const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
56afb13afcSAdam Balogh   if (!Func)
57afb13afcSAdam Balogh     return;
58afb13afcSAdam Balogh 
59afb13afcSAdam Balogh   if (Func->isOverloadedOperator() &&
60afb13afcSAdam Balogh       isComparisonOperator(Func->getOverloadedOperator())) {
61afb13afcSAdam Balogh     // Check for comparisons of iterators of different containers
62afb13afcSAdam Balogh     if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
63afb13afcSAdam Balogh       if (Call.getNumArgs() < 1)
64afb13afcSAdam Balogh         return;
65afb13afcSAdam Balogh 
66afb13afcSAdam Balogh       if (!isIteratorType(InstCall->getCXXThisExpr()->getType()) ||
67afb13afcSAdam Balogh           !isIteratorType(Call.getArgExpr(0)->getType()))
68afb13afcSAdam Balogh         return;
69afb13afcSAdam Balogh 
70afb13afcSAdam Balogh       verifyMatch(C, InstCall->getCXXThisVal(), Call.getArgSVal(0));
71afb13afcSAdam Balogh     } else {
72afb13afcSAdam Balogh       if (Call.getNumArgs() < 2)
73afb13afcSAdam Balogh         return;
74afb13afcSAdam Balogh 
75afb13afcSAdam Balogh       if (!isIteratorType(Call.getArgExpr(0)->getType()) ||
76afb13afcSAdam Balogh           !isIteratorType(Call.getArgExpr(1)->getType()))
77afb13afcSAdam Balogh         return;
78afb13afcSAdam Balogh 
79afb13afcSAdam Balogh       verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1));
80afb13afcSAdam Balogh     }
81afb13afcSAdam Balogh   } else if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
82afb13afcSAdam Balogh     const auto *ContReg = InstCall->getCXXThisVal().getAsRegion();
83afb13afcSAdam Balogh     if (!ContReg)
84afb13afcSAdam Balogh       return;
85afb13afcSAdam Balogh     // Check for erase, insert and emplace using iterator of another container
86afb13afcSAdam Balogh     if (isEraseCall(Func) || isEraseAfterCall(Func)) {
87afb13afcSAdam Balogh       verifyMatch(C, Call.getArgSVal(0),
88afb13afcSAdam Balogh                   InstCall->getCXXThisVal().getAsRegion());
89afb13afcSAdam Balogh       if (Call.getNumArgs() == 2) {
90afb13afcSAdam Balogh         verifyMatch(C, Call.getArgSVal(1),
91afb13afcSAdam Balogh                     InstCall->getCXXThisVal().getAsRegion());
92afb13afcSAdam Balogh       }
93afb13afcSAdam Balogh     } else if (isInsertCall(Func)) {
94afb13afcSAdam Balogh       verifyMatch(C, Call.getArgSVal(0),
95afb13afcSAdam Balogh                   InstCall->getCXXThisVal().getAsRegion());
96afb13afcSAdam Balogh       if (Call.getNumArgs() == 3 &&
97afb13afcSAdam Balogh           isIteratorType(Call.getArgExpr(1)->getType()) &&
98afb13afcSAdam Balogh           isIteratorType(Call.getArgExpr(2)->getType())) {
99afb13afcSAdam Balogh         verifyMatch(C, Call.getArgSVal(1), Call.getArgSVal(2));
100afb13afcSAdam Balogh       }
101afb13afcSAdam Balogh     } else if (isEmplaceCall(Func)) {
102afb13afcSAdam Balogh       verifyMatch(C, Call.getArgSVal(0),
103afb13afcSAdam Balogh                   InstCall->getCXXThisVal().getAsRegion());
104afb13afcSAdam Balogh     }
105afb13afcSAdam Balogh   } else if (isa<CXXConstructorCall>(&Call)) {
106afb13afcSAdam Balogh     // Check match of first-last iterator pair in a constructor of a container
107afb13afcSAdam Balogh     if (Call.getNumArgs() < 2)
108afb13afcSAdam Balogh       return;
109afb13afcSAdam Balogh 
110afb13afcSAdam Balogh     const auto *Ctr = cast<CXXConstructorDecl>(Call.getDecl());
111afb13afcSAdam Balogh     if (Ctr->getNumParams() < 2)
112afb13afcSAdam Balogh       return;
113afb13afcSAdam Balogh 
114afb13afcSAdam Balogh     if (Ctr->getParamDecl(0)->getName() != "first" ||
115afb13afcSAdam Balogh         Ctr->getParamDecl(1)->getName() != "last")
116afb13afcSAdam Balogh       return;
117afb13afcSAdam Balogh 
118afb13afcSAdam Balogh     if (!isIteratorType(Call.getArgExpr(0)->getType()) ||
119afb13afcSAdam Balogh         !isIteratorType(Call.getArgExpr(1)->getType()))
120afb13afcSAdam Balogh       return;
121afb13afcSAdam Balogh 
122afb13afcSAdam Balogh     verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1));
123afb13afcSAdam Balogh   } else {
124afb13afcSAdam Balogh     // The main purpose of iterators is to abstract away from different
125afb13afcSAdam Balogh     // containers and provide a (maybe limited) uniform access to them.
126afb13afcSAdam Balogh     // This implies that any correctly written template function that
127afb13afcSAdam Balogh     // works on multiple containers using iterators takes different
128afb13afcSAdam Balogh     // template parameters for different containers. So we can safely
129afb13afcSAdam Balogh     // assume that passing iterators of different containers as arguments
130afb13afcSAdam Balogh     // whose type replaces the same template parameter is a bug.
131afb13afcSAdam Balogh     //
132afb13afcSAdam Balogh     // Example:
133afb13afcSAdam Balogh     // template<typename I1, typename I2>
134afb13afcSAdam Balogh     // void f(I1 first1, I1 last1, I2 first2, I2 last2);
135afb13afcSAdam Balogh     //
136afb13afcSAdam Balogh     // In this case the first two arguments to f() must be iterators must belong
137afb13afcSAdam Balogh     // to the same container and the last to also to the same container but
138afb13afcSAdam Balogh     // not necessarily to the same as the first two.
139afb13afcSAdam Balogh 
140afb13afcSAdam Balogh     const auto *Templ = Func->getPrimaryTemplate();
141afb13afcSAdam Balogh     if (!Templ)
142afb13afcSAdam Balogh       return;
143afb13afcSAdam Balogh 
144afb13afcSAdam Balogh     const auto *TParams = Templ->getTemplateParameters();
145afb13afcSAdam Balogh     const auto *TArgs = Func->getTemplateSpecializationArgs();
146afb13afcSAdam Balogh 
147afb13afcSAdam Balogh     // Iterate over all the template parameters
148afb13afcSAdam Balogh     for (size_t I = 0; I < TParams->size(); ++I) {
149afb13afcSAdam Balogh       const auto *TPDecl = dyn_cast<TemplateTypeParmDecl>(TParams->getParam(I));
150afb13afcSAdam Balogh       if (!TPDecl)
151afb13afcSAdam Balogh         continue;
152afb13afcSAdam Balogh 
153afb13afcSAdam Balogh       if (TPDecl->isParameterPack())
154afb13afcSAdam Balogh         continue;
155afb13afcSAdam Balogh 
156afb13afcSAdam Balogh       const auto TAType = TArgs->get(I).getAsType();
157afb13afcSAdam Balogh       if (!isIteratorType(TAType))
158afb13afcSAdam Balogh         continue;
159afb13afcSAdam Balogh 
160afb13afcSAdam Balogh       SVal LHS = UndefinedVal();
161afb13afcSAdam Balogh 
162afb13afcSAdam Balogh       // For every template parameter which is an iterator type in the
163afb13afcSAdam Balogh       // instantiation look for all functions' parameters' type by it and
164afb13afcSAdam Balogh       // check whether they belong to the same container
165afb13afcSAdam Balogh       for (auto J = 0U; J < Func->getNumParams(); ++J) {
166afb13afcSAdam Balogh         const auto *Param = Func->getParamDecl(J);
167afb13afcSAdam Balogh         const auto *ParamType =
168afb13afcSAdam Balogh             Param->getType()->getAs<SubstTemplateTypeParmType>();
169bcd9ba2bSMatheus Izvekov         if (!ParamType)
170bcd9ba2bSMatheus Izvekov           continue;
171bcd9ba2bSMatheus Izvekov         const TemplateTypeParmDecl *D = ParamType->getReplacedParameter();
172bcd9ba2bSMatheus Izvekov         if (D != TPDecl)
173afb13afcSAdam Balogh           continue;
174afb13afcSAdam Balogh         if (LHS.isUndef()) {
175afb13afcSAdam Balogh           LHS = Call.getArgSVal(J);
176afb13afcSAdam Balogh         } else {
177afb13afcSAdam Balogh           verifyMatch(C, LHS, Call.getArgSVal(J));
178afb13afcSAdam Balogh         }
179afb13afcSAdam Balogh       }
180afb13afcSAdam Balogh     }
181afb13afcSAdam Balogh   }
182afb13afcSAdam Balogh }
183afb13afcSAdam Balogh 
checkPreStmt(const BinaryOperator * BO,CheckerContext & C) const1849e63b190SAdam Balogh void MismatchedIteratorChecker::checkPreStmt(const BinaryOperator *BO,
1859e63b190SAdam Balogh                                              CheckerContext &C) const {
1869e63b190SAdam Balogh   if (!BO->isComparisonOp())
1879e63b190SAdam Balogh     return;
1889e63b190SAdam Balogh 
1899e63b190SAdam Balogh   ProgramStateRef State = C.getState();
1909e63b190SAdam Balogh   SVal LVal = State->getSVal(BO->getLHS(), C.getLocationContext());
1919e63b190SAdam Balogh   SVal RVal = State->getSVal(BO->getRHS(), C.getLocationContext());
1929e63b190SAdam Balogh   verifyMatch(C, LVal, RVal);
1939e63b190SAdam Balogh }
1949e63b190SAdam Balogh 
verifyMatch(CheckerContext & C,SVal Iter,const MemRegion * Cont) const195*8ee3dfd7SBalazs Benics void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, SVal Iter,
196afb13afcSAdam Balogh                                             const MemRegion *Cont) const {
197afb13afcSAdam Balogh   // Verify match between a container and the container of an iterator
198afb13afcSAdam Balogh   Cont = Cont->getMostDerivedObjectRegion();
199afb13afcSAdam Balogh 
200afb13afcSAdam Balogh   if (const auto *ContSym = Cont->getSymbolicBase()) {
201afb13afcSAdam Balogh     if (isa<SymbolConjured>(ContSym->getSymbol()))
202afb13afcSAdam Balogh       return;
203afb13afcSAdam Balogh   }
204afb13afcSAdam Balogh 
205afb13afcSAdam Balogh   auto State = C.getState();
206afb13afcSAdam Balogh   const auto *Pos = getIteratorPosition(State, Iter);
207afb13afcSAdam Balogh   if (!Pos)
208afb13afcSAdam Balogh     return;
209afb13afcSAdam Balogh 
210afb13afcSAdam Balogh   const auto *IterCont = Pos->getContainer();
211afb13afcSAdam Balogh 
212afb13afcSAdam Balogh   // Skip symbolic regions based on conjured symbols. Two conjured symbols
213afb13afcSAdam Balogh   // may or may not be the same. For example, the same function can return
214afb13afcSAdam Balogh   // the same or a different container but we get different conjured symbols
215afb13afcSAdam Balogh   // for each call. This may cause false positives so omit them from the check.
216afb13afcSAdam Balogh   if (const auto *ContSym = IterCont->getSymbolicBase()) {
217afb13afcSAdam Balogh     if (isa<SymbolConjured>(ContSym->getSymbol()))
218afb13afcSAdam Balogh       return;
219afb13afcSAdam Balogh   }
220afb13afcSAdam Balogh 
221afb13afcSAdam Balogh   if (IterCont != Cont) {
222afb13afcSAdam Balogh     auto *N = C.generateNonFatalErrorNode(State);
223afb13afcSAdam Balogh     if (!N) {
224afb13afcSAdam Balogh       return;
225afb13afcSAdam Balogh     }
226afb13afcSAdam Balogh     reportBug("Container accessed using foreign iterator argument.",
227afb13afcSAdam Balogh                         Iter, Cont, C, N);
228afb13afcSAdam Balogh   }
229afb13afcSAdam Balogh }
230afb13afcSAdam Balogh 
verifyMatch(CheckerContext & C,SVal Iter1,SVal Iter2) const231*8ee3dfd7SBalazs Benics void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, SVal Iter1,
232*8ee3dfd7SBalazs Benics                                             SVal Iter2) const {
233afb13afcSAdam Balogh   // Verify match between the containers of two iterators
234afb13afcSAdam Balogh   auto State = C.getState();
235afb13afcSAdam Balogh   const auto *Pos1 = getIteratorPosition(State, Iter1);
236afb13afcSAdam Balogh   if (!Pos1)
237afb13afcSAdam Balogh     return;
238afb13afcSAdam Balogh 
239afb13afcSAdam Balogh   const auto *IterCont1 = Pos1->getContainer();
240afb13afcSAdam Balogh 
241afb13afcSAdam Balogh   // Skip symbolic regions based on conjured symbols. Two conjured symbols
242afb13afcSAdam Balogh   // may or may not be the same. For example, the same function can return
243afb13afcSAdam Balogh   // the same or a different container but we get different conjured symbols
244afb13afcSAdam Balogh   // for each call. This may cause false positives so omit them from the check.
245afb13afcSAdam Balogh   if (const auto *ContSym = IterCont1->getSymbolicBase()) {
246afb13afcSAdam Balogh     if (isa<SymbolConjured>(ContSym->getSymbol()))
247afb13afcSAdam Balogh       return;
248afb13afcSAdam Balogh   }
249afb13afcSAdam Balogh 
250afb13afcSAdam Balogh   const auto *Pos2 = getIteratorPosition(State, Iter2);
251afb13afcSAdam Balogh   if (!Pos2)
252afb13afcSAdam Balogh     return;
253afb13afcSAdam Balogh 
254afb13afcSAdam Balogh   const auto *IterCont2 = Pos2->getContainer();
255afb13afcSAdam Balogh   if (const auto *ContSym = IterCont2->getSymbolicBase()) {
256afb13afcSAdam Balogh     if (isa<SymbolConjured>(ContSym->getSymbol()))
257afb13afcSAdam Balogh       return;
258afb13afcSAdam Balogh   }
259afb13afcSAdam Balogh 
260afb13afcSAdam Balogh   if (IterCont1 != IterCont2) {
261afb13afcSAdam Balogh     auto *N = C.generateNonFatalErrorNode(State);
262afb13afcSAdam Balogh     if (!N)
263afb13afcSAdam Balogh       return;
264afb13afcSAdam Balogh     reportBug("Iterators of different containers used where the "
265afb13afcSAdam Balogh                         "same container is expected.", Iter1, Iter2, C, N);
266afb13afcSAdam Balogh   }
267afb13afcSAdam Balogh }
268afb13afcSAdam Balogh 
reportBug(StringRef Message,SVal Val1,SVal Val2,CheckerContext & C,ExplodedNode * ErrNode) const269*8ee3dfd7SBalazs Benics void MismatchedIteratorChecker::reportBug(StringRef Message, SVal Val1,
270*8ee3dfd7SBalazs Benics                                           SVal Val2, CheckerContext &C,
271afb13afcSAdam Balogh                                           ExplodedNode *ErrNode) const {
27218f219c5SBalazs Benics   auto R = std::make_unique<PathSensitiveBugReport>(MismatchedBugType, Message,
273afb13afcSAdam Balogh                                                     ErrNode);
274afb13afcSAdam Balogh   R->markInteresting(Val1);
275afb13afcSAdam Balogh   R->markInteresting(Val2);
276afb13afcSAdam Balogh   C.emitReport(std::move(R));
277afb13afcSAdam Balogh }
278afb13afcSAdam Balogh 
reportBug(StringRef Message,SVal Val,const MemRegion * Reg,CheckerContext & C,ExplodedNode * ErrNode) const279*8ee3dfd7SBalazs Benics void MismatchedIteratorChecker::reportBug(StringRef Message, SVal Val,
2807619050cSBalazs Benics                                           const MemRegion *Reg,
281afb13afcSAdam Balogh                                           CheckerContext &C,
282afb13afcSAdam Balogh                                           ExplodedNode *ErrNode) const {
28318f219c5SBalazs Benics   auto R = std::make_unique<PathSensitiveBugReport>(MismatchedBugType, Message,
284afb13afcSAdam Balogh                                                     ErrNode);
285afb13afcSAdam Balogh   R->markInteresting(Val);
286afb13afcSAdam Balogh   R->markInteresting(Reg);
287afb13afcSAdam Balogh   C.emitReport(std::move(R));
288afb13afcSAdam Balogh }
289afb13afcSAdam Balogh 
registerMismatchedIteratorChecker(CheckerManager & mgr)290afb13afcSAdam Balogh void ento::registerMismatchedIteratorChecker(CheckerManager &mgr) {
291afb13afcSAdam Balogh   mgr.registerChecker<MismatchedIteratorChecker>();
292afb13afcSAdam Balogh }
293afb13afcSAdam Balogh 
shouldRegisterMismatchedIteratorChecker(const CheckerManager & mgr)294bda3dd0dSKirstóf Umann bool ento::shouldRegisterMismatchedIteratorChecker(const CheckerManager &mgr) {
295afb13afcSAdam Balogh   return true;
296afb13afcSAdam Balogh }
297