================
@@ -0,0 +1,382 @@
+#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+using namespace ento;
+
+REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
+REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SymbolRef, LifetimeSourceSet)
+
+REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMapVal, const MemRegion *,
+                               LifetimeSourceSet)
+REGISTER_SET_WITH_PROGRAMSTATE(DeadSourceSet, const MemRegion *)
+
+namespace {
+class LifetimeAnnotations
+    : public Checker<check::PostCall, check::EndFunction, check::LifetimeEnd,
+                     check::Location> {
+public:
+  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+  void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
+                  const char *Sep) const override;
+  ProgramStateRef bindValues(ProgramStateRef State, SVal RetVal,
+                             const MemRegion *Source) const;
+  bool hasDanglingSource(const MemRegion *Source, ProgramStateRef State,
+                         CheckerContext &C) const;
+  void reportDanglingSource(const MemRegion *Region, ExplodedNode *N,
+                            CheckerContext &C) const;
+  void reportUseAfterScope(const MemRegion *Region, ExplodedNode *N,
+                           CheckerContext &C) const;
+
+  template <typename MapTy, typename KeyTy>
+  void checkReturnedBorrower(const MapTy &Map, const KeyTy RetKey,
+                             ProgramStateRef State, ExplodedNode *N,
+                             CheckerContext &C) const;
+  void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
+  void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const;
+  void checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
+                     CheckerContext &C) const;
+  void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
+
+  const BugType BugMsg{this, "LifetimeAnnotations", "LifetimeBound"};
+};
+
+} // namespace
+
+ProgramStateRef LifetimeAnnotations::bindValues(ProgramStateRef State,
+                                                SVal RetVal,
+                                                const MemRegion *Source) const 
{
+  LifetimeSourceSet::Factory &F =
+      State->getStateManager().get_context<LifetimeSourceSet>();
+
+  if (SymbolRef RetValSym = RetVal.getAsSymbol(/*IncludeBaseRegions=*/true)) {
+    const LifetimeSourceSet *LBSet = State->get<LifetimeBoundMap>(RetValSym);
+    LifetimeSourceSet Set = LBSet ? *LBSet : F.getEmptySet();
+    Set = F.add(Set, Source);
+    State = State->set<LifetimeBoundMap>(RetValSym, Set);
+  } else if (const MemRegion *RetValRegion = RetVal.getAsRegion()) {
+    const LifetimeSourceSet *LBValSet =
+        State->get<LifetimeBoundMapVal>(RetValRegion);
+    LifetimeSourceSet Set = LBValSet ? *LBValSet : F.getEmptySet();
+    Set = F.add(Set, Source);
+    State = State->set<LifetimeBoundMapVal>(RetValRegion, Set);
+  }
+  return State;
+}
+
+void LifetimeAnnotations::checkPostCall(const CallEvent &Call,
+                                        CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+
+  const auto *FC = dyn_cast<AnyFunctionCall>(&Call);
+  if (!FC)
+    return;
+
+  const FunctionDecl *FD = FC->getDecl();
+  if (!FD)
+    return;
+
+  SVal RetVal = Call.getReturnValue();
+
+  for (const ParmVarDecl *PVD : FD->parameters()) {
+    if (PVD->hasAttr<LifetimeBoundAttr>()) {
+      unsigned Idx = PVD->getFunctionScopeIndex();
+      SVal Arg = Call.getArgSVal(Idx);
+      if (const MemRegion *ArgValRegion = Arg.getAsRegion())
+        State = bindValues(State, RetVal, ArgValRegion);
+    }
+  }
+
+  if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
+    if (lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
+      if (const MemRegion *AttrRegion = IC->getCXXThisVal().getAsRegion()) {
+        State = bindValues(State, RetVal, AttrRegion);
+      }
+    }
+  }
+  C.addTransition(State);
+}
+
+bool LifetimeAnnotations::hasDanglingSource(const MemRegion *Source,
+                                            ProgramStateRef State,
+                                            CheckerContext &C) const {
+  // FIXME: Currently the checker only focuses on stack MemRegions only since
+  // that is the scope of week 3. Sources without a stack region are not
+  // covered, but should be implemented as well next step.
+  if (const auto *StackSpace =
+          Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
+    const StackFrame *SF = StackSpace->getStackFrame();
+    const StackFrame *CurrentSF = C.getStackFrame();
+    if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
+      return true;
+  }
+  return false;
+}
+
+template <typename MapTy, typename KeyTy>
+void LifetimeAnnotations::checkReturnedBorrower(const MapTy &Map,
+                                                const KeyTy RetKey,
+                                                ProgramStateRef State,
+                                                ExplodedNode *N,
+                                                CheckerContext &C) const {
+  for (auto &&[Origin, SourceSet] : Map) {
+    if (Origin == RetKey) {
+      for (const MemRegion *Region : SourceSet) {
+        if (hasDanglingSource(Region, State, C))
+          reportDanglingSource(Region, N, C);
+      }
+    }
+  }
+}
+
+void LifetimeAnnotations::checkEndFunction(const ReturnStmt *RS,
+                                           CheckerContext &C) const {
+  if (!RS)
+    return;
+
+  ProgramStateRef State = C.getState();
+  auto LBMap = State->get<LifetimeBoundMap>();
+  auto LBMapVal = State->get<LifetimeBoundMapVal>();
+
+  if (LBMap.isEmpty() && LBMapVal.isEmpty())
+    return;
+
+  const Expr *RetExpr = RS->getRetValue();
+  if (!RetExpr)
+    return;
+
+  RetExpr = RetExpr->IgnoreParens();
+  SVal RetVal = C.getSVal(RetExpr);
+
+  SymbolRef RetValSym = RetVal.getAsSymbol(/*IncludeBaseRegions=*/true);
+  const MemRegion *RetValRegion = RetVal.getAsRegion();
+  if (!RetValSym && !RetValRegion)
+    return;
+
+  ExplodedNode *N = C.generateNonFatalErrorNode();
+  if (!N)
+    return;
+
+  if (RetValSym)
+    checkReturnedBorrower(LBMap, RetValSym, State, N, C);
+
+  if (RetValRegion)
+    checkReturnedBorrower(LBMapVal, RetValRegion, State, N, C);
+}
+
+void LifetimeAnnotations::checkLifetimeEnd(const VarDecl *VD,
+                                           CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  if (!VD)
+    return;
+
+  SVal SourceVal = State->getLValue(VD, C.getStackFrame());
+  if (const MemRegion *SourceRegion = SourceVal.getAsRegion()) {
+    State = State->add<DeadSourceSet>(SourceRegion);
+    C.addTransition(State);
+  }
+}
+
+// FIXME: Use helper functions for checkLocation
+void LifetimeAnnotations::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
+                                        CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  auto LBMap = State->get<LifetimeBoundMap>();
+  auto LBMapVal = State->get<LifetimeBoundMapVal>();
+
+  // FIXME: Because of the CFG::LifetimeEnd elements now the analyzer can
+  // reason about out-of-scope dangling pointer deref even if there is
+  // no annotations in the source code.
+  if (const MemRegion *R = Loc.getAsRegion()) {
+    if (State->contains<DeadSourceSet>(R)) {
+      if (ExplodedNode *N = C.generateNonFatalErrorNode())
+        reportUseAfterScope(R, N, C);
+    }
+  }
+
+  if (LBMap.isEmpty() && LBMapVal.isEmpty())
+    return;
+
+  // FIXME: If a borrower has multiple bound sources the callback warns
+  // if any source has died. The callback should track which source the
+  // borrower actually points.
+  if (SymbolRef LocSym = Loc.getAsSymbol(true)) {
+    if (auto *SourceSet = State->get<LifetimeBoundMap>(LocSym)) {
+      for (const MemRegion *Source : *SourceSet) {
+        if (State->contains<DeadSourceSet>(Source)) {
+          if (ExplodedNode *N = C.generateNonFatalErrorNode())
+            reportUseAfterScope(Source, N, C);
+        }
+      }
+    }
+  }
+
+  if (const MemRegion *LocRegion = Loc.getAsRegion()) {
+    if (auto *SourceSet = State->get<LifetimeBoundMapVal>(LocRegion)) {
+      for (const MemRegion *Source : *SourceSet) {
+        if (State->contains<DeadSourceSet>(Source)) {
+          if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
+            reportUseAfterScope(Source, N, C);
+          }
+        }
+      }
+    }
+  }
+}
+
+void LifetimeAnnotations::reportDanglingSource(const MemRegion *Region,
+                                               ExplodedNode *N,
+                                               CheckerContext &C) const {
+  std::string ErrorMessage =
+      (llvm::Twine("Returning value bound to a local '") + Region->getString() 
+
+       "' that will go out of scope")
+          .str();
+  auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, ErrorMessage, N);
+  C.emitReport(std::move(BR));
+}
+
+void LifetimeAnnotations::reportUseAfterScope(const MemRegion *Region,
+                                              ExplodedNode *N,
+                                              CheckerContext &C) const {
+  std::string ErrorMessage = (llvm::Twine("Use of '") + Region->getString() +
+                              "' after its lifetime ended.")
+                                 .str();
+  auto BR = std::make_unique<PathSensitiveBugReport>(BugMsg, ErrorMessage, N);
+  C.emitReport(std::move(BR));
+}
+
+void LifetimeAnnotations::checkDeadSymbols(SymbolReaper &SymReaper,
+                                           CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+
+  // Get both maps since both of them needs to be cleaned up
+  LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
+  LifetimeBoundMapValTy LBMapVal = State->get<LifetimeBoundMapVal>();
+
+  for (const SymExpr *Sym : llvm::make_first_range(LBMap)) {
+    if (!SymReaper.isLive(Sym))
+      State = State->remove<LifetimeBoundMap>(Sym);
+  }
+
+  for (const MemRegion *Region : llvm::make_first_range(LBMapVal)) {
+    if (!SymReaper.isLiveRegion(Region))
+      State = State->remove<LifetimeBoundMapVal>(Region);
+  }
+  if (State != C.getState())
+    C.addTransition(State);
+}
+
+void LifetimeAnnotations::printState(raw_ostream &Out, ProgramStateRef State,
+                                     const char *NL, const char *Sep) const {
+  auto LBMap = State->get<LifetimeBoundMap>();
+  auto LBMapVal = State->get<LifetimeBoundMapVal>();
+
+  if (LBMap.isEmpty() && LBMapVal.isEmpty())
+    return;
+
+  Out << Sep << "LifetimeBound bindings:" << NL;
+  for (auto &&[OriginSym, SourceSet] : LBMap) {
+    for (const auto *Region : SourceSet)
+      Out << " Origin " << OriginSym << " contains Loan " << Region << NL;
+  }
+  for (auto &&[OriginRegion, SourceSet] : LBMapVal) {
+    for (const auto *Region : SourceSet)
+      Out << " Origin " << OriginRegion << " contains Loan " << Region << NL;
+  }
+}
+
+namespace {
+class DebugLifetimeAnnotations : public Checker<eval::Call> {
----------------
isuckatcs wrote:

Is it common that we have multiple checkers in the same source files? I think 
the patterns is that we create one source file for each checker. Can you please 
move this checker to its own source file?

https://github.com/llvm/llvm-project/pull/200145
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to