Coverage Report

Created: 2023-09-21 18:56

/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp
Line
Count
Source (jump to first uncovered line)
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
2
MismatchedIteratorChecker::MismatchedIteratorChecker() {
57
2
  MismatchedBugType.reset(
58
2
      new BugType(this, "Iterator(s) mismatched", "Misuse of STL APIs",
59
2
                  /*SuppressOnSink=*/true));
60
2
}
61
62
void MismatchedIteratorChecker::checkPreCall(const CallEvent &Call,
63
370
                                             CheckerContext &C) const {
64
  // Check for iterator mismatches
65
370
  const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
66
370
  if (!Func)
67
0
    return;
68
69
370
  if (Func->isOverloadedOperator() &&
70
370
      
isComparisonOperator(Func->getOverloadedOperator())46
) {
71
    // Check for comparisons of iterators of different containers
72
14
    if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
73
14
      if (Call.getNumArgs() < 1)
74
0
        return;
75
76
14
      if (!isIteratorType(InstCall->getCXXThisExpr()->getType()) ||
77
14
          !isIteratorType(Call.getArgExpr(0)->getType()))
78
0
        return;
79
80
14
      verifyMatch(C, InstCall->getCXXThisVal(), Call.getArgSVal(0));
81
14
    } else {
82
0
      if (Call.getNumArgs() < 2)
83
0
        return;
84
85
0
      if (!isIteratorType(Call.getArgExpr(0)->getType()) ||
86
0
          !isIteratorType(Call.getArgExpr(1)->getType()))
87
0
        return;
88
89
0
      verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1));
90
0
    }
91
356
  } else if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
92
222
    const auto *ContReg = InstCall->getCXXThisVal().getAsRegion();
93
222
    if (!ContReg)
94
0
      return;
95
    // Check for erase, insert and emplace using iterator of another container
96
222
    if (isEraseCall(Func) || 
isEraseAfterCall(Func)208
) {
97
14
      verifyMatch(C, Call.getArgSVal(0),
98
14
                  InstCall->getCXXThisVal().getAsRegion());
99
14
      if (Call.getNumArgs() == 2) {
100
8
        verifyMatch(C, Call.getArgSVal(1),
101
8
                    InstCall->getCXXThisVal().getAsRegion());
102
8
      }
103
208
    } else if (isInsertCall(Func)) {
104
22
      verifyMatch(C, Call.getArgSVal(0),
105
22
                  InstCall->getCXXThisVal().getAsRegion());
106
22
      if (Call.getNumArgs() == 3 &&
107
22
          
isIteratorType(Call.getArgExpr(1)->getType())12
&&
108
22
          
isIteratorType(Call.getArgExpr(2)->getType())8
) {
109
8
        verifyMatch(C, Call.getArgSVal(1), Call.getArgSVal(2));
110
8
      }
111
186
    } else if (isEmplaceCall(Func)) {
112
4
      verifyMatch(C, Call.getArgSVal(0),
113
4
                  InstCall->getCXXThisVal().getAsRegion());
114
4
    }
115
222
  } else 
if (134
isa<CXXConstructorCall>(&Call)134
) {
116
    // Check match of first-last iterator pair in a constructor of a container
117
108
    if (Call.getNumArgs() < 2)
118
104
      return;
119
120
4
    const auto *Ctr = cast<CXXConstructorDecl>(Call.getDecl());
121
4
    if (Ctr->getNumParams() < 2)
122
0
      return;
123
124
4
    if (Ctr->getParamDecl(0)->getName() != "first" ||
125
4
        Ctr->getParamDecl(1)->getName() != "last")
126
0
      return;
127
128
4
    if (!isIteratorType(Call.getArgExpr(0)->getType()) ||
129
4
        !isIteratorType(Call.getArgExpr(1)->getType()))
130
0
      return;
131
132
4
    verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1));
133
26
  } 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
26
    const auto *Templ = Func->getPrimaryTemplate();
151
26
    if (!Templ)
152
8
      return;
153
154
18
    const auto *TParams = Templ->getTemplateParameters();
155
18
    const auto *TArgs = Func->getTemplateSpecializationArgs();
156
157
    // Iterate over all the template parameters
158
54
    for (size_t I = 0; I < TParams->size(); 
++I36
) {
159
36
      const auto *TPDecl = dyn_cast<TemplateTypeParmDecl>(TParams->getParam(I));
160
36
      if (!TPDecl)
161
0
        continue;
162
163
36
      if (TPDecl->isParameterPack())
164
0
        continue;
165
166
36
      const auto TAType = TArgs->get(I).getAsType();
167
36
      if (!isIteratorType(TAType))
168
6
        continue;
169
170
30
      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
132
      for (auto J = 0U; J < Func->getNumParams(); 
++J102
) {
176
102
        const auto *Param = Func->getParamDecl(J);
177
102
        const auto *ParamType =
178
102
            Param->getType()->getAs<SubstTemplateTypeParmType>();
179
102
        if (!ParamType)
180
6
          continue;
181
96
        const TemplateTypeParmDecl *D = ParamType->getReplacedParameter();
182
96
        if (D != TPDecl)
183
48
          continue;
184
48
        if (LHS.isUndef()) {
185
26
          LHS = Call.getArgSVal(J);
186
26
        } else {
187
22
          verifyMatch(C, LHS, Call.getArgSVal(J));
188
22
        }
189
48
      }
190
30
    }
191
18
  }
192
370
}
193
194
void MismatchedIteratorChecker::checkPreStmt(const BinaryOperator *BO,
195
25
                                             CheckerContext &C) const {
196
25
  if (!BO->isComparisonOp())
197
16
    return;
198
199
9
  ProgramStateRef State = C.getState();
200
9
  SVal LVal = State->getSVal(BO->getLHS(), C.getLocationContext());
201
9
  SVal RVal = State->getSVal(BO->getRHS(), C.getLocationContext());
202
9
  verifyMatch(C, LVal, RVal);
203
9
}
204
205
void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, const SVal &Iter,
206
48
                                            const MemRegion *Cont) const {
207
  // Verify match between a container and the container of an iterator
208
48
  Cont = Cont->getMostDerivedObjectRegion();
209
210
48
  if (const auto *ContSym = Cont->getSymbolicBase()) {
211
48
    if (isa<SymbolConjured>(ContSym->getSymbol()))
212
2
      return;
213
48
  }
214
215
46
  auto State = C.getState();
216
46
  const auto *Pos = getIteratorPosition(State, Iter);
217
46
  if (!Pos)
218
0
    return;
219
220
46
  const auto *IterCont = Pos->getContainer();
221
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
46
  if (const auto *ContSym = IterCont->getSymbolicBase()) {
227
46
    if (isa<SymbolConjured>(ContSym->getSymbol()))
228
0
      return;
229
46
  }
230
231
46
  if (IterCont != Cont) {
232
20
    auto *N = C.generateNonFatalErrorNode(State);
233
20
    if (!N) {
234
2
      return;
235
2
    }
236
18
    reportBug("Container accessed using foreign iterator argument.",
237
18
                        Iter, Cont, C, N);
238
18
  }
239
46
}
240
241
void MismatchedIteratorChecker::verifyMatch(CheckerContext &C,
242
                                            const SVal &Iter1,
243
57
                                            const SVal &Iter2) const {
244
  // Verify match between the containers of two iterators
245
57
  auto State = C.getState();
246
57
  const auto *Pos1 = getIteratorPosition(State, Iter1);
247
57
  if (!Pos1)
248
7
    return;
249
250
50
  const auto *IterCont1 = Pos1->getContainer();
251
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
50
  if (const auto *ContSym = IterCont1->getSymbolicBase()) {
257
50
    if (isa<SymbolConjured>(ContSym->getSymbol()))
258
2
      return;
259
50
  }
260
261
48
  const auto *Pos2 = getIteratorPosition(State, Iter2);
262
48
  if (!Pos2)
263
0
    return;
264
265
48
  const auto *IterCont2 = Pos2->getContainer();
266
48
  if (const auto *ContSym = IterCont2->getSymbolicBase()) {
267
48
    if (isa<SymbolConjured>(ContSym->getSymbol()))
268
0
      return;
269
48
  }
270
271
48
  if (IterCont1 != IterCont2) {
272
16
    auto *N = C.generateNonFatalErrorNode(State);
273
16
    if (!N)
274
0
      return;
275
16
    reportBug("Iterators of different containers used where the "
276
16
                        "same container is expected.", Iter1, Iter2, C, N);
277
16
  }
278
48
}
279
280
void MismatchedIteratorChecker::reportBug(const StringRef &Message,
281
                                          const SVal &Val1,
282
                                          const SVal &Val2,
283
                                          CheckerContext &C,
284
16
                                          ExplodedNode *ErrNode) const {
285
16
  auto R = std::make_unique<PathSensitiveBugReport>(*MismatchedBugType, Message,
286
16
                                                    ErrNode);
287
16
  R->markInteresting(Val1);
288
16
  R->markInteresting(Val2);
289
16
  C.emitReport(std::move(R));
290
16
}
291
292
void MismatchedIteratorChecker::reportBug(const StringRef &Message,
293
                                          const SVal &Val, const MemRegion *Reg,
294
                                          CheckerContext &C,
295
18
                                          ExplodedNode *ErrNode) const {
296
18
  auto R = std::make_unique<PathSensitiveBugReport>(*MismatchedBugType, Message,
297
18
                                                    ErrNode);
298
18
  R->markInteresting(Val);
299
18
  R->markInteresting(Reg);
300
18
  C.emitReport(std::move(R));
301
18
}
302
303
2
void ento::registerMismatchedIteratorChecker(CheckerManager &mgr) {
304
2
  mgr.registerChecker<MismatchedIteratorChecker>();
305
2
}
306
307
4
bool ento::shouldRegisterMismatchedIteratorChecker(const CheckerManager &mgr) {
308
4
  return true;
309
4
}