xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp (revision 9a08a3fab9993f9b93167de5c783dfed6dd7efc0)
1 //===-- IteratorModeling.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 modeling-checker for modeling STL iterator-like iterators.
10 //
11 //===----------------------------------------------------------------------===//
12 //
13 // In the code, iterator can be represented as a:
14 // * type-I: typedef-ed pointer. Operations over such iterator, such as
15 //           comparisons or increments, are modeled straightforwardly by the
16 //           analyzer.
17 // * type-II: structure with its method bodies available.  Operations over such
18 //            iterator are inlined by the analyzer, and results of modeling
19 //            these operations are exposing implementation details of the
20 //            iterators, which is not necessarily helping.
21 // * type-III: completely opaque structure. Operations over such iterator are
22 //             modeled conservatively, producing conjured symbols everywhere.
23 //
24 // To handle all these types in a common way we introduce a structure called
25 // IteratorPosition which is an abstraction of the position the iterator
26 // represents using symbolic expressions. The checker handles all the
27 // operations on this structure.
28 //
29 // Additionally, depending on the circumstances, operators of types II and III
30 // can be represented as:
31 // * type-IIa, type-IIIa: conjured structure symbols - when returned by value
32 //                        from conservatively evaluated methods such as
33 //                        `.begin()`.
34 // * type-IIb, type-IIIb: memory regions of iterator-typed objects, such as
35 //                        variables or temporaries, when the iterator object is
36 //                        currently treated as an lvalue.
37 // * type-IIc, type-IIIc: compound values of iterator-typed objects, when the
38 //                        iterator object is treated as an rvalue taken of a
39 //                        particular lvalue, eg. a copy of "type-a" iterator
40 //                        object, or an iterator that existed before the
41 //                        analysis has started.
42 //
43 // To handle any of these three different representations stored in an SVal we
44 // use setter and getters functions which separate the three cases. To store
45 // them we use a pointer union of symbol and memory region.
46 //
47 // The checker works the following way: We record the begin and the
48 // past-end iterator for all containers whenever their `.begin()` and `.end()`
49 // are called. Since the Constraint Manager cannot handle such SVals we need
50 // to take over its role. We post-check equality and non-equality comparisons
51 // and record that the two sides are equal if we are in the 'equal' branch
52 // (true-branch for `==` and false-branch for `!=`).
53 //
54 // In case of type-I or type-II iterators we get a concrete integer as a result
55 // of the comparison (1 or 0) but in case of type-III we only get a Symbol. In
56 // this latter case we record the symbol and reload it in evalAssume() and do
57 // the propagation there. We also handle (maybe double) negated comparisons
58 // which are represented in the form of (x == 0 or x != 0) where x is the
59 // comparison itself.
60 //
61 // Since `SimpleConstraintManager` cannot handle complex symbolic expressions
62 // we only use expressions of the format S, S+n or S-n for iterator positions
63 // where S is a conjured symbol and n is an unsigned concrete integer. When
64 // making an assumption e.g. `S1 + n == S2 + m` we store `S1 - S2 == m - n` as
65 // a constraint which we later retrieve when doing an actual comparison.
66 
67 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
68 #include "clang/AST/DeclTemplate.h"
69 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
70 #include "clang/StaticAnalyzer/Core/Checker.h"
71 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
72 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
73 #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h"
74 
75 #include "Iterator.h"
76 
77 #include <utility>
78 
79 using namespace clang;
80 using namespace ento;
81 using namespace iterator;
82 
83 namespace {
84 
85 class IteratorModeling
86     : public Checker<check::PostCall, check::PostStmt<MaterializeTemporaryExpr>,
87                      check::Bind, check::LiveSymbols, check::DeadSymbols> {
88 
89   void handleComparison(CheckerContext &C, const Expr *CE, SVal RetVal,
90                         const SVal &LVal, const SVal &RVal,
91                         OverloadedOperatorKind Op) const;
92   void processComparison(CheckerContext &C, ProgramStateRef State,
93                          SymbolRef Sym1, SymbolRef Sym2, const SVal &RetVal,
94                          OverloadedOperatorKind Op) const;
95   void handleIncrement(CheckerContext &C, const SVal &RetVal, const SVal &Iter,
96                        bool Postfix) const;
97   void handleDecrement(CheckerContext &C, const SVal &RetVal, const SVal &Iter,
98                        bool Postfix) const;
99   void handleRandomIncrOrDecr(CheckerContext &C, const Expr *CE,
100                               OverloadedOperatorKind Op, const SVal &RetVal,
101                               const SVal &LHS, const SVal &RHS) const;
102   void assignToContainer(CheckerContext &C, const Expr *CE, const SVal &RetVal,
103                          const MemRegion *Cont) const;
104   void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
105                   const char *Sep) const override;
106 
107 public:
108   IteratorModeling() {}
109 
110   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
111   void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &C) const;
112   void checkPostStmt(const CXXConstructExpr *CCE, CheckerContext &C) const;
113   void checkPostStmt(const DeclStmt *DS, CheckerContext &C) const;
114   void checkPostStmt(const MaterializeTemporaryExpr *MTE,
115                      CheckerContext &C) const;
116   void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const;
117   void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
118 };
119 
120 bool isSimpleComparisonOperator(OverloadedOperatorKind OK);
121 ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val);
122 ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1,
123                               SymbolRef Sym2, bool Equal);
124 bool isBoundThroughLazyCompoundVal(const Environment &Env,
125                                    const MemRegion *Reg);
126 
127 } // namespace
128 
129 void IteratorModeling::checkPostCall(const CallEvent &Call,
130                                      CheckerContext &C) const {
131   // Record new iterator positions and iterator position changes
132   const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
133   if (!Func)
134     return;
135 
136   if (Func->isOverloadedOperator()) {
137     const auto Op = Func->getOverloadedOperator();
138     if (isSimpleComparisonOperator(Op)) {
139       const auto *OrigExpr = Call.getOriginExpr();
140       if (!OrigExpr)
141         return;
142 
143       if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
144         handleComparison(C, OrigExpr, Call.getReturnValue(),
145                          InstCall->getCXXThisVal(), Call.getArgSVal(0), Op);
146         return;
147       }
148 
149       handleComparison(C, OrigExpr, Call.getReturnValue(), Call.getArgSVal(0),
150                          Call.getArgSVal(1), Op);
151       return;
152     } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) {
153       const auto *OrigExpr = Call.getOriginExpr();
154       if (!OrigExpr)
155         return;
156 
157       if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
158         if (Call.getNumArgs() >= 1 &&
159               Call.getArgExpr(0)->getType()->isIntegralOrEnumerationType()) {
160           handleRandomIncrOrDecr(C, OrigExpr, Func->getOverloadedOperator(),
161                                  Call.getReturnValue(),
162                                  InstCall->getCXXThisVal(), Call.getArgSVal(0));
163           return;
164         }
165       } else {
166         if (Call.getNumArgs() >= 2 &&
167               Call.getArgExpr(1)->getType()->isIntegralOrEnumerationType()) {
168           handleRandomIncrOrDecr(C, OrigExpr, Func->getOverloadedOperator(),
169                                  Call.getReturnValue(), Call.getArgSVal(0),
170                                  Call.getArgSVal(1));
171           return;
172         }
173       }
174     } else if (isIncrementOperator(Func->getOverloadedOperator())) {
175       if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
176         handleIncrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(),
177                         Call.getNumArgs());
178         return;
179       }
180 
181       handleIncrement(C, Call.getReturnValue(), Call.getArgSVal(0),
182                       Call.getNumArgs());
183       return;
184     } else if (isDecrementOperator(Func->getOverloadedOperator())) {
185       if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
186         handleDecrement(C, Call.getReturnValue(), InstCall->getCXXThisVal(),
187                         Call.getNumArgs());
188         return;
189       }
190 
191       handleDecrement(C, Call.getReturnValue(), Call.getArgSVal(0),
192                         Call.getNumArgs());
193       return;
194     }
195   } else {
196     if (!isIteratorType(Call.getResultType()))
197       return;
198 
199     const auto *OrigExpr = Call.getOriginExpr();
200     if (!OrigExpr)
201       return;
202 
203     auto State = C.getState();
204 
205     // Already bound to container?
206     if (getIteratorPosition(State, Call.getReturnValue()))
207       return;
208 
209     // Copy-like and move constructors
210     if (isa<CXXConstructorCall>(&Call) && Call.getNumArgs() == 1) {
211       if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(0))) {
212         State = setIteratorPosition(State, Call.getReturnValue(), *Pos);
213         if (cast<CXXConstructorDecl>(Func)->isMoveConstructor()) {
214           State = removeIteratorPosition(State, Call.getArgSVal(0));
215         }
216         C.addTransition(State);
217         return;
218       }
219     }
220 
221     // Assumption: if return value is an iterator which is not yet bound to a
222     //             container, then look for the first iterator argument, and
223     //             bind the return value to the same container. This approach
224     //             works for STL algorithms.
225     // FIXME: Add a more conservative mode
226     for (unsigned i = 0; i < Call.getNumArgs(); ++i) {
227       if (isIteratorType(Call.getArgExpr(i)->getType())) {
228         if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(i))) {
229           assignToContainer(C, OrigExpr, Call.getReturnValue(),
230                             Pos->getContainer());
231           return;
232         }
233       }
234     }
235   }
236 }
237 
238 void IteratorModeling::checkBind(SVal Loc, SVal Val, const Stmt *S,
239                                  CheckerContext &C) const {
240   auto State = C.getState();
241   const auto *Pos = getIteratorPosition(State, Val);
242   if (Pos) {
243     State = setIteratorPosition(State, Loc, *Pos);
244     C.addTransition(State);
245   } else {
246     const auto *OldPos = getIteratorPosition(State, Loc);
247     if (OldPos) {
248       State = removeIteratorPosition(State, Loc);
249       C.addTransition(State);
250     }
251   }
252 }
253 
254 void IteratorModeling::checkPostStmt(const MaterializeTemporaryExpr *MTE,
255                                      CheckerContext &C) const {
256   /* Transfer iterator state to temporary objects */
257   auto State = C.getState();
258   const auto *Pos = getIteratorPosition(State, C.getSVal(MTE->getSubExpr()));
259   if (!Pos)
260     return;
261   State = setIteratorPosition(State, C.getSVal(MTE), *Pos);
262   C.addTransition(State);
263 }
264 
265 void IteratorModeling::checkLiveSymbols(ProgramStateRef State,
266                                         SymbolReaper &SR) const {
267   // Keep symbolic expressions of iterator positions alive
268   auto RegionMap = State->get<IteratorRegionMap>();
269   for (const auto &Reg : RegionMap) {
270     const auto Offset = Reg.second.getOffset();
271     for (auto i = Offset->symbol_begin(); i != Offset->symbol_end(); ++i)
272       if (isa<SymbolData>(*i))
273         SR.markLive(*i);
274   }
275 
276   auto SymbolMap = State->get<IteratorSymbolMap>();
277   for (const auto &Sym : SymbolMap) {
278     const auto Offset = Sym.second.getOffset();
279     for (auto i = Offset->symbol_begin(); i != Offset->symbol_end(); ++i)
280       if (isa<SymbolData>(*i))
281         SR.markLive(*i);
282   }
283 
284 }
285 
286 void IteratorModeling::checkDeadSymbols(SymbolReaper &SR,
287                                         CheckerContext &C) const {
288   // Cleanup
289   auto State = C.getState();
290 
291   auto RegionMap = State->get<IteratorRegionMap>();
292   for (const auto &Reg : RegionMap) {
293     if (!SR.isLiveRegion(Reg.first)) {
294       // The region behind the `LazyCompoundVal` is often cleaned up before
295       // the `LazyCompoundVal` itself. If there are iterator positions keyed
296       // by these regions their cleanup must be deferred.
297       if (!isBoundThroughLazyCompoundVal(State->getEnvironment(), Reg.first)) {
298         State = State->remove<IteratorRegionMap>(Reg.first);
299       }
300     }
301   }
302 
303   auto SymbolMap = State->get<IteratorSymbolMap>();
304   for (const auto &Sym : SymbolMap) {
305     if (!SR.isLive(Sym.first)) {
306       State = State->remove<IteratorSymbolMap>(Sym.first);
307     }
308   }
309 
310   C.addTransition(State);
311 }
312 
313 void IteratorModeling::handleComparison(CheckerContext &C, const Expr *CE,
314                                        SVal RetVal, const SVal &LVal,
315                                        const SVal &RVal,
316                                        OverloadedOperatorKind Op) const {
317   // Record the operands and the operator of the comparison for the next
318   // evalAssume, if the result is a symbolic expression. If it is a concrete
319   // value (only one branch is possible), then transfer the state between
320   // the operands according to the operator and the result
321    auto State = C.getState();
322   const auto *LPos = getIteratorPosition(State, LVal);
323   const auto *RPos = getIteratorPosition(State, RVal);
324   const MemRegion *Cont = nullptr;
325   if (LPos) {
326     Cont = LPos->getContainer();
327   } else if (RPos) {
328     Cont = RPos->getContainer();
329   }
330   if (!Cont)
331     return;
332 
333   // At least one of the iterators have recorded positions. If one of them has
334   // not then create a new symbol for the offset.
335   SymbolRef Sym;
336   if (!LPos || !RPos) {
337     auto &SymMgr = C.getSymbolManager();
338     Sym = SymMgr.conjureSymbol(CE, C.getLocationContext(),
339                                C.getASTContext().LongTy, C.blockCount());
340     State = assumeNoOverflow(State, Sym, 4);
341   }
342 
343   if (!LPos) {
344     State = setIteratorPosition(State, LVal,
345                                 IteratorPosition::getPosition(Cont, Sym));
346     LPos = getIteratorPosition(State, LVal);
347   } else if (!RPos) {
348     State = setIteratorPosition(State, RVal,
349                                 IteratorPosition::getPosition(Cont, Sym));
350     RPos = getIteratorPosition(State, RVal);
351   }
352 
353   // We cannot make assumpotions on `UnknownVal`. Let us conjure a symbol
354   // instead.
355   if (RetVal.isUnknown()) {
356     auto &SymMgr = C.getSymbolManager();
357     auto *LCtx = C.getLocationContext();
358     RetVal = nonloc::SymbolVal(SymMgr.conjureSymbol(
359         CE, LCtx, C.getASTContext().BoolTy, C.blockCount()));
360     State = State->BindExpr(CE, LCtx, RetVal);
361   }
362 
363   processComparison(C, State, LPos->getOffset(), RPos->getOffset(), RetVal, Op);
364 }
365 
366 void IteratorModeling::processComparison(CheckerContext &C,
367                                          ProgramStateRef State, SymbolRef Sym1,
368                                          SymbolRef Sym2, const SVal &RetVal,
369                                          OverloadedOperatorKind Op) const {
370   if (const auto TruthVal = RetVal.getAs<nonloc::ConcreteInt>()) {
371     if ((State = relateSymbols(State, Sym1, Sym2,
372                               (Op == OO_EqualEqual) ==
373                                (TruthVal->getValue() != 0)))) {
374       C.addTransition(State);
375     } else {
376       C.generateSink(State, C.getPredecessor());
377     }
378     return;
379   }
380 
381   const auto ConditionVal = RetVal.getAs<DefinedSVal>();
382   if (!ConditionVal)
383     return;
384 
385   if (auto StateTrue = relateSymbols(State, Sym1, Sym2, Op == OO_EqualEqual)) {
386     StateTrue = StateTrue->assume(*ConditionVal, true);
387     C.addTransition(StateTrue);
388   }
389 
390   if (auto StateFalse = relateSymbols(State, Sym1, Sym2, Op != OO_EqualEqual)) {
391     StateFalse = StateFalse->assume(*ConditionVal, false);
392     C.addTransition(StateFalse);
393   }
394 }
395 
396 void IteratorModeling::handleIncrement(CheckerContext &C, const SVal &RetVal,
397                                        const SVal &Iter, bool Postfix) const {
398   // Increment the symbolic expressions which represents the position of the
399   // iterator
400   auto State = C.getState();
401   auto &BVF = C.getSymbolManager().getBasicVals();
402 
403   const auto *Pos = getIteratorPosition(State, Iter);
404   if (!Pos)
405     return;
406 
407   auto NewState =
408     advancePosition(State, Iter, OO_Plus,
409                     nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))));
410   assert(NewState &&
411          "Advancing position by concrete int should always be successful");
412 
413   const auto *NewPos = getIteratorPosition(NewState, Iter);
414   assert(NewPos &&
415          "Iterator should have position after successful advancement");
416 
417   State = setIteratorPosition(State, Iter, *NewPos);
418   State = setIteratorPosition(State, RetVal, Postfix ? *Pos : *NewPos);
419   C.addTransition(State);
420 }
421 
422 void IteratorModeling::handleDecrement(CheckerContext &C, const SVal &RetVal,
423                                        const SVal &Iter, bool Postfix) const {
424   // Decrement the symbolic expressions which represents the position of the
425   // iterator
426   auto State = C.getState();
427   auto &BVF = C.getSymbolManager().getBasicVals();
428 
429   const auto *Pos = getIteratorPosition(State, Iter);
430   if (!Pos)
431     return;
432 
433   auto NewState =
434     advancePosition(State, Iter, OO_Minus,
435                     nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))));
436   assert(NewState &&
437          "Advancing position by concrete int should always be successful");
438 
439   const auto *NewPos = getIteratorPosition(NewState, Iter);
440   assert(NewPos &&
441          "Iterator should have position after successful advancement");
442 
443   State = setIteratorPosition(State, Iter, *NewPos);
444   State = setIteratorPosition(State, RetVal, Postfix ? *Pos : *NewPos);
445   C.addTransition(State);
446 }
447 
448 void IteratorModeling::handleRandomIncrOrDecr(CheckerContext &C,
449                                               const Expr *CE,
450                                               OverloadedOperatorKind Op,
451                                               const SVal &RetVal,
452                                               const SVal &LHS,
453                                               const SVal &RHS) const {
454   // Increment or decrement the symbolic expressions which represents the
455   // position of the iterator
456   auto State = C.getState();
457 
458   const auto *Pos = getIteratorPosition(State, LHS);
459   if (!Pos)
460     return;
461 
462   const auto *value = &RHS;
463   if (auto loc = RHS.getAs<Loc>()) {
464     const auto val = State->getRawSVal(*loc);
465     value = &val;
466   }
467 
468   auto &TgtVal = (Op == OO_PlusEqual || Op == OO_MinusEqual) ? LHS : RetVal;
469 
470   auto NewState =
471     advancePosition(State, LHS, Op, *value);
472   if (NewState) {
473     const auto *NewPos = getIteratorPosition(NewState, LHS);
474     assert(NewPos &&
475            "Iterator should have position after successful advancement");
476 
477     State = setIteratorPosition(NewState, TgtVal, *NewPos);
478     C.addTransition(State);
479   } else {
480     assignToContainer(C, CE, TgtVal, Pos->getContainer());
481   }
482 }
483 
484 void IteratorModeling::assignToContainer(CheckerContext &C, const Expr *CE,
485                                          const SVal &RetVal,
486                                          const MemRegion *Cont) const {
487   Cont = Cont->getMostDerivedObjectRegion();
488 
489   auto State = C.getState();
490   auto &SymMgr = C.getSymbolManager();
491   auto Sym = SymMgr.conjureSymbol(CE, C.getLocationContext(),
492                                   C.getASTContext().LongTy, C.blockCount());
493   State = assumeNoOverflow(State, Sym, 4);
494   State = setIteratorPosition(State, RetVal,
495                               IteratorPosition::getPosition(Cont, Sym));
496   C.addTransition(State);
497 }
498 
499 void IteratorModeling::printState(raw_ostream &Out, ProgramStateRef State,
500                                   const char *NL, const char *Sep) const {
501   auto SymbolMap = State->get<IteratorSymbolMap>();
502   auto RegionMap = State->get<IteratorRegionMap>();
503 
504   if (!SymbolMap.isEmpty() || !RegionMap.isEmpty()) {
505     Out << Sep << "Iterator Positions :" << NL;
506     for (const auto &Sym : SymbolMap) {
507       Sym.first->dumpToStream(Out);
508       Out << " : ";
509       const auto Pos = Sym.second;
510       Out << (Pos.isValid() ? "Valid" : "Invalid") << " ; Container == ";
511       Pos.getContainer()->dumpToStream(Out);
512       Out<<" ; Offset == ";
513       Pos.getOffset()->dumpToStream(Out);
514     }
515 
516     for (const auto &Reg : RegionMap) {
517       Reg.first->dumpToStream(Out);
518       Out << " : ";
519       const auto Pos = Reg.second;
520       Out << (Pos.isValid() ? "Valid" : "Invalid") << " ; Container == ";
521       Pos.getContainer()->dumpToStream(Out);
522       Out<<" ; Offset == ";
523       Pos.getOffset()->dumpToStream(Out);
524     }
525   }
526 }
527 
528 namespace {
529 
530 bool isSimpleComparisonOperator(OverloadedOperatorKind OK) {
531   return OK == OO_EqualEqual || OK == OO_ExclaimEqual;
532 }
533 
534 ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val) {
535   if (auto Reg = Val.getAsRegion()) {
536     Reg = Reg->getMostDerivedObjectRegion();
537     return State->remove<IteratorRegionMap>(Reg);
538   } else if (const auto Sym = Val.getAsSymbol()) {
539     return State->remove<IteratorSymbolMap>(Sym);
540   } else if (const auto LCVal = Val.getAs<nonloc::LazyCompoundVal>()) {
541     return State->remove<IteratorRegionMap>(LCVal->getRegion());
542   }
543   return nullptr;
544 }
545 
546 ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1,
547                               SymbolRef Sym2, bool Equal) {
548   auto &SVB = State->getStateManager().getSValBuilder();
549 
550   // FIXME: This code should be reworked as follows:
551   // 1. Subtract the operands using evalBinOp().
552   // 2. Assume that the result doesn't overflow.
553   // 3. Compare the result to 0.
554   // 4. Assume the result of the comparison.
555   const auto comparison =
556     SVB.evalBinOp(State, BO_EQ, nonloc::SymbolVal(Sym1),
557                   nonloc::SymbolVal(Sym2), SVB.getConditionType());
558 
559   assert(comparison.getAs<DefinedSVal>() &&
560     "Symbol comparison must be a `DefinedSVal`");
561 
562   auto NewState = State->assume(comparison.castAs<DefinedSVal>(), Equal);
563   if (!NewState)
564     return nullptr;
565 
566   if (const auto CompSym = comparison.getAsSymbol()) {
567     assert(isa<SymIntExpr>(CompSym) &&
568            "Symbol comparison must be a `SymIntExpr`");
569     assert(BinaryOperator::isComparisonOp(
570                cast<SymIntExpr>(CompSym)->getOpcode()) &&
571            "Symbol comparison must be a comparison");
572     return assumeNoOverflow(NewState, cast<SymIntExpr>(CompSym)->getLHS(), 2);
573   }
574 
575   return NewState;
576 }
577 
578 bool isBoundThroughLazyCompoundVal(const Environment &Env,
579                                    const MemRegion *Reg) {
580   for (const auto &Binding : Env) {
581     if (const auto LCVal = Binding.second.getAs<nonloc::LazyCompoundVal>()) {
582       if (LCVal->getRegion() == Reg)
583         return true;
584     }
585   }
586 
587   return false;
588 }
589 
590 } // namespace
591 
592 void ento::registerIteratorModeling(CheckerManager &mgr) {
593   mgr.registerChecker<IteratorModeling>();
594 }
595 
596 bool ento::shouldRegisterIteratorModeling(const LangOptions &LO) {
597   return true;
598 }
599