xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp (revision 700637cbb5e582861067a11aaca4d053546871d2)
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 
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"
20 
21 
22 #include "Iterator.h"
23 
24 using namespace clang;
25 using namespace ento;
26 using namespace iterator;
27 
28 namespace {
29 
30 class 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 
44 public:
45   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
46   void checkPreStmt(const BinaryOperator *BO, CheckerContext &C) const;
47 
48 };
49 
50 } // namespace
51 
checkPreCall(const CallEvent & Call,CheckerContext & C) const52 void 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
86     if (isEraseCall(Func) || isEraseAfterCall(Func)) {
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 
checkPreStmt(const BinaryOperator * BO,CheckerContext & C) const190 void 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 
verifyMatch(CheckerContext & C,SVal Iter,const MemRegion * Cont) const201 void 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 
verifyMatch(CheckerContext & C,SVal Iter1,SVal Iter2) const237 void 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 
reportBug(StringRef Message,SVal Val1,SVal Val2,CheckerContext & C,ExplodedNode * ErrNode) const275 void 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 
reportBug(StringRef Message,SVal Val,const MemRegion * Reg,CheckerContext & C,ExplodedNode * ErrNode) const285 void 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 
registerMismatchedIteratorChecker(CheckerManager & mgr)296 void ento::registerMismatchedIteratorChecker(CheckerManager &mgr) {
297   mgr.registerChecker<MismatchedIteratorChecker>();
298 }
299 
shouldRegisterMismatchedIteratorChecker(const CheckerManager & mgr)300 bool ento::shouldRegisterMismatchedIteratorChecker(const CheckerManager &mgr) {
301   return true;
302 }
303