xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp (revision 1db9f3b21e39176dd5b67cf8ac378633b172463e)
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 
52 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       verifyMatch(C, Call.getArgSVal(0),
95                   InstCall->getCXXThisVal().getAsRegion());
96       if (Call.getNumArgs() == 3 &&
97           isIteratorType(Call.getArgExpr(1)->getType()) &&
98           isIteratorType(Call.getArgExpr(2)->getType())) {
99         verifyMatch(C, Call.getArgSVal(1), Call.getArgSVal(2));
100       }
101     } else if (isEmplaceCall(Func)) {
102       verifyMatch(C, Call.getArgSVal(0),
103                   InstCall->getCXXThisVal().getAsRegion());
104     }
105   } else if (isa<CXXConstructorCall>(&Call)) {
106     // Check match of first-last iterator pair in a constructor of a container
107     if (Call.getNumArgs() < 2)
108       return;
109 
110     const auto *Ctr = cast<CXXConstructorDecl>(Call.getDecl());
111     if (Ctr->getNumParams() < 2)
112       return;
113 
114     if (Ctr->getParamDecl(0)->getName() != "first" ||
115         Ctr->getParamDecl(1)->getName() != "last")
116       return;
117 
118     if (!isIteratorType(Call.getArgExpr(0)->getType()) ||
119         !isIteratorType(Call.getArgExpr(1)->getType()))
120       return;
121 
122     verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1));
123   } else {
124     // The main purpose of iterators is to abstract away from different
125     // containers and provide a (maybe limited) uniform access to them.
126     // This implies that any correctly written template function that
127     // works on multiple containers using iterators takes different
128     // template parameters for different containers. So we can safely
129     // assume that passing iterators of different containers as arguments
130     // whose type replaces the same template parameter is a bug.
131     //
132     // Example:
133     // template<typename I1, typename I2>
134     // void f(I1 first1, I1 last1, I2 first2, I2 last2);
135     //
136     // In this case the first two arguments to f() must be iterators must belong
137     // to the same container and the last to also to the same container but
138     // not necessarily to the same as the first two.
139 
140     const auto *Templ = Func->getPrimaryTemplate();
141     if (!Templ)
142       return;
143 
144     const auto *TParams = Templ->getTemplateParameters();
145     const auto *TArgs = Func->getTemplateSpecializationArgs();
146 
147     // Iterate over all the template parameters
148     for (size_t I = 0; I < TParams->size(); ++I) {
149       const auto *TPDecl = dyn_cast<TemplateTypeParmDecl>(TParams->getParam(I));
150       if (!TPDecl)
151         continue;
152 
153       if (TPDecl->isParameterPack())
154         continue;
155 
156       const auto TAType = TArgs->get(I).getAsType();
157       if (!isIteratorType(TAType))
158         continue;
159 
160       SVal LHS = UndefinedVal();
161 
162       // For every template parameter which is an iterator type in the
163       // instantiation look for all functions' parameters' type by it and
164       // check whether they belong to the same container
165       for (auto J = 0U; J < Func->getNumParams(); ++J) {
166         const auto *Param = Func->getParamDecl(J);
167         const auto *ParamType =
168             Param->getType()->getAs<SubstTemplateTypeParmType>();
169         if (!ParamType)
170           continue;
171         const TemplateTypeParmDecl *D = ParamType->getReplacedParameter();
172         if (D != TPDecl)
173           continue;
174         if (LHS.isUndef()) {
175           LHS = Call.getArgSVal(J);
176         } else {
177           verifyMatch(C, LHS, Call.getArgSVal(J));
178         }
179       }
180     }
181   }
182 }
183 
184 void MismatchedIteratorChecker::checkPreStmt(const BinaryOperator *BO,
185                                              CheckerContext &C) const {
186   if (!BO->isComparisonOp())
187     return;
188 
189   ProgramStateRef State = C.getState();
190   SVal LVal = State->getSVal(BO->getLHS(), C.getLocationContext());
191   SVal RVal = State->getSVal(BO->getRHS(), C.getLocationContext());
192   verifyMatch(C, LVal, RVal);
193 }
194 
195 void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, SVal Iter,
196                                             const MemRegion *Cont) const {
197   // Verify match between a container and the container of an iterator
198   Cont = Cont->getMostDerivedObjectRegion();
199 
200   if (const auto *ContSym = Cont->getSymbolicBase()) {
201     if (isa<SymbolConjured>(ContSym->getSymbol()))
202       return;
203   }
204 
205   auto State = C.getState();
206   const auto *Pos = getIteratorPosition(State, Iter);
207   if (!Pos)
208     return;
209 
210   const auto *IterCont = Pos->getContainer();
211 
212   // Skip symbolic regions based on conjured symbols. Two conjured symbols
213   // may or may not be the same. For example, the same function can return
214   // the same or a different container but we get different conjured symbols
215   // for each call. This may cause false positives so omit them from the check.
216   if (const auto *ContSym = IterCont->getSymbolicBase()) {
217     if (isa<SymbolConjured>(ContSym->getSymbol()))
218       return;
219   }
220 
221   if (IterCont != Cont) {
222     auto *N = C.generateNonFatalErrorNode(State);
223     if (!N) {
224       return;
225     }
226     reportBug("Container accessed using foreign iterator argument.",
227                         Iter, Cont, C, N);
228   }
229 }
230 
231 void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, SVal Iter1,
232                                             SVal Iter2) const {
233   // Verify match between the containers of two iterators
234   auto State = C.getState();
235   const auto *Pos1 = getIteratorPosition(State, Iter1);
236   if (!Pos1)
237     return;
238 
239   const auto *IterCont1 = Pos1->getContainer();
240 
241   // Skip symbolic regions based on conjured symbols. Two conjured symbols
242   // may or may not be the same. For example, the same function can return
243   // the same or a different container but we get different conjured symbols
244   // for each call. This may cause false positives so omit them from the check.
245   if (const auto *ContSym = IterCont1->getSymbolicBase()) {
246     if (isa<SymbolConjured>(ContSym->getSymbol()))
247       return;
248   }
249 
250   const auto *Pos2 = getIteratorPosition(State, Iter2);
251   if (!Pos2)
252     return;
253 
254   const auto *IterCont2 = Pos2->getContainer();
255   if (const auto *ContSym = IterCont2->getSymbolicBase()) {
256     if (isa<SymbolConjured>(ContSym->getSymbol()))
257       return;
258   }
259 
260   if (IterCont1 != IterCont2) {
261     auto *N = C.generateNonFatalErrorNode(State);
262     if (!N)
263       return;
264     reportBug("Iterators of different containers used where the "
265                         "same container is expected.", Iter1, Iter2, C, N);
266   }
267 }
268 
269 void MismatchedIteratorChecker::reportBug(StringRef Message, SVal Val1,
270                                           SVal Val2, CheckerContext &C,
271                                           ExplodedNode *ErrNode) const {
272   auto R = std::make_unique<PathSensitiveBugReport>(MismatchedBugType, Message,
273                                                     ErrNode);
274   R->markInteresting(Val1);
275   R->markInteresting(Val2);
276   C.emitReport(std::move(R));
277 }
278 
279 void MismatchedIteratorChecker::reportBug(StringRef Message, SVal Val,
280                                           const MemRegion *Reg,
281                                           CheckerContext &C,
282                                           ExplodedNode *ErrNode) const {
283   auto R = std::make_unique<PathSensitiveBugReport>(MismatchedBugType, Message,
284                                                     ErrNode);
285   R->markInteresting(Val);
286   R->markInteresting(Reg);
287   C.emitReport(std::move(R));
288 }
289 
290 void ento::registerMismatchedIteratorChecker(CheckerManager &mgr) {
291   mgr.registerChecker<MismatchedIteratorChecker>();
292 }
293 
294 bool ento::shouldRegisterMismatchedIteratorChecker(const CheckerManager &mgr) {
295   return true;
296 }
297