xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp (revision ec0ea6efa1ad229d75c394c1a9b9cac33af2b1d3)
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   std::unique_ptr<BugType> MismatchedBugType;
34 
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;
45 
46 public:
47   MismatchedIteratorChecker();
48 
49   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
50   void checkPreStmt(const BinaryOperator *BO, CheckerContext &C) const;
51 
52 };
53 
54 } // namespace
55 
56 MismatchedIteratorChecker::MismatchedIteratorChecker() {
57   MismatchedBugType.reset(
58       new BugType(this, "Iterator(s) mismatched", "Misuse of STL APIs",
59                   /*SuppressOnSink=*/true));
60 }
61 
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());
66   if (!Func)
67     return;
68 
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)
74         return;
75 
76       if (!isIteratorType(InstCall->getCXXThisExpr()->getType()) ||
77           !isIteratorType(Call.getArgExpr(0)->getType()))
78         return;
79 
80       verifyMatch(C, InstCall->getCXXThisVal(), Call.getArgSVal(0));
81     } else {
82       if (Call.getNumArgs() < 2)
83         return;
84 
85       if (!isIteratorType(Call.getArgExpr(0)->getType()) ||
86           !isIteratorType(Call.getArgExpr(1)->getType()))
87         return;
88 
89       verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1));
90     }
91   } else if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
92     const auto *ContReg = InstCall->getCXXThisVal().getAsRegion();
93     if (!ContReg)
94       return;
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());
102       }
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));
110       }
111     } else if (isEmplaceCall(Func)) {
112       verifyMatch(C, Call.getArgSVal(0),
113                   InstCall->getCXXThisVal().getAsRegion());
114     }
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)
118       return;
119 
120     const auto *Ctr = cast<CXXConstructorDecl>(Call.getDecl());
121     if (Ctr->getNumParams() < 2)
122       return;
123 
124     if (Ctr->getParamDecl(0)->getName() != "first" ||
125         Ctr->getParamDecl(1)->getName() != "last")
126       return;
127 
128     if (!isIteratorType(Call.getArgExpr(0)->getType()) ||
129         !isIteratorType(Call.getArgExpr(1)->getType()))
130       return;
131 
132     verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1));
133   } else {
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.
141     //
142     // Example:
143     // template<typename I1, typename I2>
144     // void f(I1 first1, I1 last1, I2 first2, I2 last2);
145     //
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.
149 
150     const auto *Templ = Func->getPrimaryTemplate();
151     if (!Templ)
152       return;
153 
154     const auto *TParams = Templ->getTemplateParameters();
155     const auto *TArgs = Func->getTemplateSpecializationArgs();
156 
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));
160       if (!TPDecl)
161         continue;
162 
163       if (TPDecl->isParameterPack())
164         continue;
165 
166       const auto TAType = TArgs->get(I).getAsType();
167       if (!isIteratorType(TAType))
168         continue;
169 
170       SVal LHS = UndefinedVal();
171 
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>();
179         if (!ParamType ||
180             ParamType->getReplacedParameter()->getDecl() != TPDecl)
181           continue;
182         if (LHS.isUndef()) {
183           LHS = Call.getArgSVal(J);
184         } else {
185           verifyMatch(C, LHS, Call.getArgSVal(J));
186         }
187       }
188     }
189   }
190 }
191 
192 void MismatchedIteratorChecker::checkPreStmt(const BinaryOperator *BO,
193                                              CheckerContext &C) const {
194   if (!BO->isComparisonOp())
195     return;
196 
197   ProgramStateRef State = C.getState();
198   SVal LVal = State->getSVal(BO->getLHS(), C.getLocationContext());
199   SVal RVal = State->getSVal(BO->getRHS(), C.getLocationContext());
200   verifyMatch(C, LVal, RVal);
201 }
202 
203 void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, const SVal &Iter,
204                                             const MemRegion *Cont) const {
205   // Verify match between a container and the container of an iterator
206   Cont = Cont->getMostDerivedObjectRegion();
207 
208   if (const auto *ContSym = Cont->getSymbolicBase()) {
209     if (isa<SymbolConjured>(ContSym->getSymbol()))
210       return;
211   }
212 
213   auto State = C.getState();
214   const auto *Pos = getIteratorPosition(State, Iter);
215   if (!Pos)
216     return;
217 
218   const auto *IterCont = Pos->getContainer();
219 
220   // Skip symbolic regions based on conjured symbols. Two conjured symbols
221   // may or may not be the same. For example, the same function can return
222   // the same or a different container but we get different conjured symbols
223   // for each call. This may cause false positives so omit them from the check.
224   if (const auto *ContSym = IterCont->getSymbolicBase()) {
225     if (isa<SymbolConjured>(ContSym->getSymbol()))
226       return;
227   }
228 
229   if (IterCont != Cont) {
230     auto *N = C.generateNonFatalErrorNode(State);
231     if (!N) {
232       return;
233     }
234     reportBug("Container accessed using foreign iterator argument.",
235                         Iter, Cont, C, N);
236   }
237 }
238 
239 void MismatchedIteratorChecker::verifyMatch(CheckerContext &C,
240                                             const SVal &Iter1,
241                                             const SVal &Iter2) const {
242   // Verify match between the containers of two iterators
243   auto State = C.getState();
244   const auto *Pos1 = getIteratorPosition(State, Iter1);
245   if (!Pos1)
246     return;
247 
248   const auto *IterCont1 = Pos1->getContainer();
249 
250   // Skip symbolic regions based on conjured symbols. Two conjured symbols
251   // may or may not be the same. For example, the same function can return
252   // the same or a different container but we get different conjured symbols
253   // for each call. This may cause false positives so omit them from the check.
254   if (const auto *ContSym = IterCont1->getSymbolicBase()) {
255     if (isa<SymbolConjured>(ContSym->getSymbol()))
256       return;
257   }
258 
259   const auto *Pos2 = getIteratorPosition(State, Iter2);
260   if (!Pos2)
261     return;
262 
263   const auto *IterCont2 = Pos2->getContainer();
264   if (const auto *ContSym = IterCont2->getSymbolicBase()) {
265     if (isa<SymbolConjured>(ContSym->getSymbol()))
266       return;
267   }
268 
269   if (IterCont1 != IterCont2) {
270     auto *N = C.generateNonFatalErrorNode(State);
271     if (!N)
272       return;
273     reportBug("Iterators of different containers used where the "
274                         "same container is expected.", Iter1, Iter2, C, N);
275   }
276 }
277 
278 void MismatchedIteratorChecker::reportBug(const StringRef &Message,
279                                           const SVal &Val1,
280                                           const SVal &Val2,
281                                           CheckerContext &C,
282                                           ExplodedNode *ErrNode) const {
283   auto R = std::make_unique<PathSensitiveBugReport>(*MismatchedBugType, Message,
284                                                     ErrNode);
285   R->markInteresting(Val1);
286   R->markInteresting(Val2);
287   C.emitReport(std::move(R));
288 }
289 
290 void MismatchedIteratorChecker::reportBug(const StringRef &Message,
291                                           const SVal &Val, const MemRegion *Reg,
292                                           CheckerContext &C,
293                                           ExplodedNode *ErrNode) const {
294   auto R = std::make_unique<PathSensitiveBugReport>(*MismatchedBugType, Message,
295                                                     ErrNode);
296   R->markInteresting(Val);
297   R->markInteresting(Reg);
298   C.emitReport(std::move(R));
299 }
300 
301 void ento::registerMismatchedIteratorChecker(CheckerManager &mgr) {
302   mgr.registerChecker<MismatchedIteratorChecker>();
303 }
304 
305 bool ento::shouldRegisterMismatchedIteratorChecker(const CheckerManager &mgr) {
306   return true;
307 }
308