baloghadamsoftware created this revision.
baloghadamsoftware added reviewers: NoQ, Szelethus.
baloghadamsoftware added a project: clang.
Herald added subscribers: ASDenysPetrov, martong, steakhal, Charusso, 
gamesh411, dkrupp, donat.nagy, mikhail.ramalho, a.sidorin, rnkovacs, szepet, 
xazax.hun, whisperity.

A typical (bad) algorithm for erasing elements of a list is the following:

  for(auto i = L.begin(); i != L.end(); ++i) {
     if (condition(*i))
       i = L.erase(i);
  }

If `condition()` returns true for the last element, then `erase()` returns the 
past-the-end iterator which the loop tries to increment which leads to 
undefined behavior. However, the iterator range checker does not find this bug 
because the "loop iteration before that last iteration" cannot be recognized by 
the analyzer. Instead we added an option in this patch to `ContainerModeling` 
which enables the modeling checker to assume after every erase that the result 
is the past-the-end iterator (if this case is possible).


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D77150

Files:
  clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
  clang/lib/StaticAnalyzer/Checkers/ContainerModeling.cpp
  clang/test/Analysis/analyzer-config.c

Index: clang/test/Analysis/analyzer-config.c
===================================================================
--- clang/test/Analysis/analyzer-config.c
+++ clang/test/Analysis/analyzer-config.c
@@ -7,6 +7,7 @@
 // CHECK-NEXT: alpha.clone.CloneChecker:IgnoredFilesPattern = ""
 // CHECK-NEXT: alpha.clone.CloneChecker:MinimumCloneComplexity = 50
 // CHECK-NEXT: alpha.clone.CloneChecker:ReportNormalClones = true
+// CHECK-NEXT: alpha.cplusplus.ContainerModeling:AggressiveEraseModeling = false
 // CHECK-NEXT: alpha.cplusplus.STLAlgorithmModeling:AggressiveStdFindModeling = false
 // CHECK-NEXT: alpha.security.MmapWriteExec:MmapProtExec = 0x04
 // CHECK-NEXT: alpha.security.MmapWriteExec:MmapProtRead = 0x01
@@ -100,4 +101,4 @@
 // CHECK-NEXT: unroll-loops = false
 // CHECK-NEXT: widen-loops = false
 // CHECK-NEXT: [stats]
-// CHECK-NEXT: num-entries = 97
+// CHECK-NEXT: num-entries = 98
Index: clang/lib/StaticAnalyzer/Checkers/ContainerModeling.cpp
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/ContainerModeling.cpp
+++ clang/lib/StaticAnalyzer/Checkers/ContainerModeling.cpp
@@ -65,6 +65,8 @@
 public:
   ContainerModeling() = default;
 
+  DefaultBool AggressiveEraseModeling;
+
   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
   void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const;
   void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
@@ -606,7 +608,7 @@
   auto RetPos =
     IteratorPosition::getPosition(Pos->getContainer(), Pos->getOffset());
   State = setIteratorPosition(State, RetVal, RetPos);
-  
+
   C.addTransition(State);
 }
 
@@ -632,7 +634,7 @@
                   SymMgr.getType(Pos->getOffset())).getAsSymbol();
   auto RetPos = IteratorPosition::getPosition(Pos->getContainer(), RetOffset);
   State = setIteratorPosition(State, RetVal, RetPos);
-  
+
   C.addTransition(State);
 }
 
@@ -677,7 +679,29 @@
                   SymMgr.getType(Pos->getOffset())).getAsSymbol();
   auto RetPos = IteratorPosition::getPosition(Pos->getContainer(), RetOffset);
   State = setIteratorPosition(State, RetVal, RetPos);
-  
+
+  if (AggressiveEraseModeling) {
+    if (const auto *CData = getContainerData(State, ContReg)) {
+      if (const SymbolRef EndSym = CData->getEnd()) {
+        const auto RetEnd =
+          SVB.evalBinOp(State, BO_EQ, nonloc::SymbolVal(RetOffset),
+                        nonloc::SymbolVal(EndSym), SVB.getConditionType())
+          .getAs<DefinedOrUnknownSVal>();
+        if (RetEnd) {
+          ProgramStateRef StateEnd, StateNotEnd;
+          std::tie(StateEnd, StateNotEnd) = State->assume(*RetEnd);
+          if (StateEnd) {
+            C.addTransition(StateEnd);
+          }
+          if (StateNotEnd) {
+            C.addTransition(StateNotEnd);
+          }
+          return;
+        }
+      }
+    }
+  }
+
   C.addTransition(State);
 }
 
@@ -725,6 +749,11 @@
 
 void ContainerModeling::handleEraseAfter(CheckerContext &C, SVal Cont,
                                          SVal Iter, SVal RetVal) const {
+  const auto *ContReg = Cont.getAsRegion();
+  if (!ContReg)
+    return;
+
+  ContReg = ContReg->getMostDerivedObjectRegion();
   auto State = C.getState();
   const auto *Pos = getIteratorPosition(State, Iter);
   if (!Pos)
@@ -748,6 +777,28 @@
   auto RetPos = IteratorPosition::getPosition(Pos->getContainer(), RetOffset);
   State = setIteratorPosition(State, RetVal, RetPos);
 
+  if (AggressiveEraseModeling) {
+    if (const auto *CData = getContainerData(State, ContReg)) {
+      if (const SymbolRef EndSym = CData->getEnd()) {
+        const auto RetEnd =
+          SVB.evalBinOp(State, BO_EQ, nonloc::SymbolVal(RetOffset),
+                        nonloc::SymbolVal(EndSym), SVB.getConditionType())
+          .getAs<DefinedOrUnknownSVal>();
+        if (RetEnd) {
+          ProgramStateRef StateEnd, StateNotEnd;
+          std::tie(StateEnd, StateNotEnd) = State->assume(*RetEnd);
+          if (StateEnd) {
+            C.addTransition(StateEnd);
+          }
+          if (StateNotEnd) {
+            C.addTransition(StateNotEnd);
+          }
+          return;
+        }
+      }
+    }
+  }
+
   C.addTransition(State);
 }
 
@@ -1131,7 +1182,11 @@
 } // namespace
 
 void ento::registerContainerModeling(CheckerManager &mgr) {
-  mgr.registerChecker<ContainerModeling>();
+  auto *Checker = mgr.registerChecker<ContainerModeling>();
+  Checker->AggressiveEraseModeling =
+    mgr.getAnalyzerOptions().getCheckerBooleanOption(
+        "alpha.cplusplus.ContainerModeling", "AggressiveEraseModeling");
+
 }
 
 bool ento::shouldRegisterContainerModeling(const CheckerManager &mgr) {
Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -615,6 +615,14 @@
 
 def ContainerModeling : Checker<"ContainerModeling">,
   HelpText<"Models C++ containers">,
+  CheckerOptions<[
+    CmdLineOption<Boolean,
+                  "AggressiveEraseModeling",
+                  "Enables exploration of the past-the-end branch for the "
+                  "return value of the erase() method of containers.",
+                  "false",
+                  Released>
+  ]>,
   Documentation<NotDocumented>,
   Hidden;
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to