================
@@ -0,0 +1,438 @@
+//===- PlacementSelfCopyChecker.cpp -----------------------------*- C++ 
-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Defines a checker that finds placement new expressions whose initializer
+// reads a part of the target storage that has already been overwritten, e.g.
+// `::new (p) T(*p)`. A copy or move constructor that is not inlined is
+// reported whenever its source is the target storage.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/ParentMap.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+
+using namespace clang;
+using namespace ento;
+
+using PlacementKey = std::pair<const CXXNewExpr *, const StackFrame *>;
+REGISTER_MAP_WITH_PROGRAMSTATE(PlacementTargets, PlacementKey,
+                               const MemRegion *)
+REGISTER_SET_WITH_PROGRAMSTATE(SelfSourcedTargets, const MemRegion *)
+REGISTER_SET_WITH_PROGRAMSTATE(OverwrittenParts, const MemRegion *)
+REGISTER_TRAIT_WITH_PROGRAMSTATE(PendingSelfCopy, const MemRegion *)
+REGISTER_TRAIT_WITH_PROGRAMSTATE(ReadOverwritten, const MemRegion *)
+
+namespace {
+class PlacementSelfCopyChecker
+    : public Checker<check::PostCall, check::PreCall, check::Bind,
+                     check::Location, check::PostStmt<CXXNewExpr>> {
+  const BugType BT{this, "Placement new initialized from its own storage",
+                   categories::MemoryError};
+  const BugType ReadBT{this, "Placement new reads overwritten storage",
+                       categories::MemoryError};
+
+  void reportRead(const Expr *E, const MemRegion *Overwritten,
+                  CheckerContext &C) const;
+  void reportSelfCopy(const Expr *Arg, CheckerContext &C) const;
+  void reportPendingSelfCopy(const CallEvent &Call, CheckerContext &C) const;
+  void checkOverwrittenRead(const Stmt *S, const Expr *E,
+                            const MemRegion *Source, CheckerContext &C) const;
+
+public:
+  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
+  void checkBind(SVal Loc, SVal Val, const Stmt *S, bool AtDeclInit,
+                 CheckerContext &C) const;
+  void checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
+                     CheckerContext &C) const;
+  void checkPostStmt(const CXXNewExpr *NE, CheckerContext &C) const;
+};
+} // namespace
+
+namespace {
+struct Extent {
+  const MemRegion *Base;
+  int64_t Begin;
+  std::optional<int64_t> End;
+};
+} // namespace
+
+static std::optional<Extent> getExtent(const MemRegion *R, ASTContext &Ctx) {
+  RegionOffset Offset = R->getAsOffset();
+  if (!Offset.isValid() || Offset.hasSymbolicOffset())
+    return std::nullopt;
+  Extent E{Offset.getRegion(), Offset.getOffset(), std::nullopt};
+  if (const auto *TR = dyn_cast<TypedValueRegion>(R)) {
+    QualType Ty = TR->getValueType();
+    if (!Ty->isIncompleteType())
+      E.End = E.Begin + Ctx.getTypeSize(Ty);
+  }
+  return E;
+}
+
+static bool overlaps(const MemRegion *A, const MemRegion *B, ASTContext &Ctx) {
+  if (A == B || A->isSubRegionOf(B) || B->isSubRegionOf(A))
+    return true;
+  std::optional<Extent> EA = getExtent(A, Ctx), EB = getExtent(B, Ctx);
+  if (!EA || !EB || EA->Base != EB->Base)
+    return false;
+  return (!EB->End || EA->Begin < *EB->End) &&
+         (!EA->End || EB->Begin < *EA->End);
+}
+
+static bool isWithin(const MemRegion *R, const MemRegion *Container,
+                     ASTContext &Ctx) {
+  if (R == Container || R->isSubRegionOf(Container))
+    return true;
+  std::optional<Extent> ER = getExtent(R, Ctx), EC = getExtent(Container, Ctx);
+  if (!ER || !EC || ER->Base != EC->Base || ER->Begin < EC->Begin)
+    return false;
+  return !EC->End || (ER->End && *ER->End <= *EC->End);
+}
+
+static bool isThisBased(const Expr *E) {
+  while (E) {
+    E = E->IgnoreParenCasts();
+    if (isa<CXXThisExpr>(E))
+      return true;
+    if (const auto *ME = dyn_cast<MemberExpr>(E))
+      E = ME->getBase();
+    else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E))
+      E = ASE->getBase();
+    else if (const auto *BO = dyn_cast<BinaryOperator>(E);
+             BO && BO->isAssignmentOp())
+      E = BO->getLHS();
+    else if (const auto *UO = dyn_cast<UnaryOperator>(E);
+             UO && UO->getOpcode() == UO_Deref)
+      E = UO->getSubExpr();
+    else
+      return false;
+  }
+  return false;
+}
+
+static const MemRegion *findSelfSourcedTarget(ProgramStateRef State,
+                                              const MemRegion *R,
+                                              CheckerContext &C) {
+  for (const MemRegion *Target : State->get<SelfSourcedTargets>())
+    if (isWithin(R, Target, C.getASTContext()))
+      return Target;
+  return nullptr;
+}
+
+static const MemRegion *findOverwritten(ProgramStateRef State,
+                                        const MemRegion *R, CheckerContext &C) 
{
+  for (const MemRegion *W : State->get<OverwrittenParts>())
+    if (overlaps(R, W, C.getASTContext()))
+      return W;
+  return nullptr;
+}
+
+// CSA binds an init list only after evaluating all elements; recompute order.
+static const MemRegion *walkInitList(PlacementKey Key, const MemRegion *Target,
+                                     const Stmt *S, const MemRegion *Source,
+                                     const MemRegion *&Before,
+                                     CheckerContext &C) {
+  Before = nullptr;
+  const auto &[NE, Frame] = Key;
+  if (Frame != C.getStackFrame())
+    return nullptr;
+  const auto *Init = dyn_cast_if_present<InitListExpr>(NE->getInitializer());
+  if (!Init)
+    return nullptr;
+
+  const ParentMap &PM = C.getCurrentAnalysisDeclContext()->getParentMap();
+  SmallVector<std::pair<const InitListExpr *, unsigned>, 4> Path;
+  for (const Stmt *Child = S, *Parent = PM.getParent(S); Parent;
+       Child = Parent, Parent = PM.getParent(Parent)) {
+    if (const auto *ILE = dyn_cast<InitListExpr>(Parent)) {
+      ArrayRef<Expr *> Inits = ILE->inits();
+      const auto *It = llvm::find(Inits, Child);
+      if (It == Inits.end())
+        return nullptr;
+      Path.emplace_back(ILE, It - Inits.begin());
+    }
+    if (Parent == Init)
+      break;
+  }
+  if (Path.empty() || Path.back().first != Init)
+    return nullptr;
+
+  ASTContext &Ctx = C.getASTContext();
+  MemRegionManager &MRM = C.getStoreManager().getRegionManager();
+  SValBuilder &SVB = C.getSValBuilder();
+  const auto *R = cast<SubRegion>(Target);
+  for (const auto &[ILE, Index] : llvm::reverse(Path)) {
+    QualType Ty = ILE->getType();
+    if (const auto *AT = Ctx.getAsArrayType(Ty)) {
+      QualType ElemTy = AT->getElementType();
+      for (unsigned I = 0; I != Index && Source && !Before; ++I) {
+        const MemRegion *Elem =
+            MRM.getElementRegion(ElemTy, SVB.makeArrayIndex(I), R, Ctx);
+        if (overlaps(Source, Elem, Ctx))
+          Before = Elem;
+      }
+      R = MRM.getElementRegion(ElemTy, SVB.makeArrayIndex(Index), R, Ctx);
+      continue;
+    }
+
+    const auto *RD = Ty->getAsCXXRecordDecl();
+    if (!RD || RD->isUnion() || RD->getNumBases())
+      return nullptr;
+    unsigned I = 0;
+    const FieldRegion *Next = nullptr;
+    for (const FieldDecl *FD : RD->fields()) {
+      if (FD->isUnnamedBitField())
+        continue;
+      const FieldRegion *Field = MRM.getFieldRegion(FD, R);
+      if (I++ == Index) {
+        Next = Field;
+        break;
+      }
+      if (Source && !Before && overlaps(Source, Field, Ctx))
+        Before = Field;
+    }
+    if (!Next)
+      return nullptr;
+    R = Next;
+  }
+  return R;
+}
+
+static const MemRegion *getDestination(PlacementKey Key,
+                                       const MemRegion *Target,
+                                       const CXXConstructorCall &Ctor,
+                                       const MemRegion *This,
+                                       CheckerContext &C) {
+  const Stmt *S = Ctor.getOriginExpr();
+  if (Key.first->getConstructExpr() == S)
+    return Key.second == C.getStackFrame() ? This : nullptr;
+  const MemRegion *Before;
+  return walkInitList(Key, Target, S, nullptr, Before, C);
+}
+
+void PlacementSelfCopyChecker::checkOverwrittenRead(const Stmt *S,
+                                                    const Expr *E,
+                                                    const MemRegion *Source,
+                                                    CheckerContext &C) const {
+  if (isThisBased(E))
+    return;
+  ProgramStateRef State = C.getState();
+  Source = Source->StripCasts();
+  const MemRegion *Overwritten = findSelfSourcedTarget(State, Source, C)
+                                     ? findOverwritten(State, Source, C)
+                                     : nullptr;
+  for (const auto &[Key, Target] : State->get<PlacementTargets>()) {
+    if (Overwritten)
+      break;
+    walkInitList(Key, Target, S, Source, Overwritten, C);
+  }
+  if (Overwritten)
+    reportRead(E, Overwritten, C);
+}
+
+void PlacementSelfCopyChecker::reportRead(const Expr *E,
+                                          const MemRegion *Overwritten,
+                                          CheckerContext &C) const {
+  ExplodedNode *N =
+      C.generateErrorNode(C.getState()->set<ReadOverwritten>(Overwritten));
+  if (!N)
+    return;
+  auto R = std::make_unique<PathSensitiveBugReport>(
+      ReadBT,
+      "Placement new reads a part of its storage that it has already "
+      "overwritten",
+      N);
+  R->addRange(E->getSourceRange());
+  C.emitReport(std::move(R));
+}
+
+void PlacementSelfCopyChecker::reportSelfCopy(const Expr *Arg,
+                                              CheckerContext &C) const {
+  ExplodedNode *N = C.generateErrorNode();
+  if (!N)
+    return;
+  auto R = std::make_unique<PathSensitiveBugReport>(
+      BT,
+      "Object constructed by placement new is initialized from the storage "
+      "it is placed into",
+      N);
+  R->addRange(Arg->getSourceRange());
+  bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(Arg), *R);
+  C.emitReport(std::move(R));
+}
+
+// Reports inside the standard library are suppressed; report at the caller.
+void PlacementSelfCopyChecker::reportPendingSelfCopy(const CallEvent &Call,
+                                                     CheckerContext &C) const {
+  const MemRegion *Target = C.getState()->get<PendingSelfCopy>();
+  const Expr *Origin = dyn_cast_if_present<Expr>(Call.getOriginExpr());
+  if (!Target || !Origin ||
+      C.getSourceManager().isInSystemHeader(Origin->getBeginLoc()))
+    return;
+  for (unsigned I = 0, E = Call.getNumArgs(); I != E; ++I) {
+    const MemRegion *Arg = Call.getArgSVal(I).getAsRegion();
+    if (Arg && !Call.getArgExpr(I)->getType()->isPointerType() &&
+        overlaps(Arg->StripCasts(), Target, C.getASTContext())) {
+      reportSelfCopy(Call.getArgExpr(I), C);
+      return;
+    }
+  }
+  C.addTransition(C.getState()->remove<PendingSelfCopy>());
+}
+
+void PlacementSelfCopyChecker::checkPostCall(const CallEvent &Call,
+                                             CheckerContext &C) const {
+  if (const auto *Alloc = dyn_cast<CXXAllocatorCall>(&Call)) {
+    const CXXNewExpr *NE = Alloc->getOriginExpr();
+    const FunctionDecl *OperatorNew = NE->getOperatorNew();
+    if (!OperatorNew || !OperatorNew->isReservedGlobalPlacementOperator())
+      return;
+    const MemRegion *Target = Call.getReturnValue().getAsRegion();
+    if (!Target)
+      return;
+    C.addTransition(C.getState()->set<PlacementTargets>({NE, 
C.getStackFrame()},
+                                                        Target->StripCasts()));
+    return;
+  }
+
+  const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call);
+  if (!Ctor) {
+    reportPendingSelfCopy(Call, C);
+    return;
+  }
+  const auto *CD = dyn_cast_if_present<CXXConstructorDecl>(Ctor->getDecl());
----------------
SpaceAccordeonist wrote:

This `dyn_cast_if_present` is also redundant. `CXXConstructorDecl::getDecl()` 
returns `CXXConstructorDecl`

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

Reply via email to