[clang] [analyzer] Add alpha.cplusplus.PlacementSelfCopy checker (PR #233525)

via cfe-commits cfe-commits at lists.llvm.org
Thu Oct 22 00:56:16 PDT 2026


================
@@ -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,
----------------
SpaceAccordeonist wrote:

I propose breaking this method up into three or four subfunctions, although it's more about code smells. It seems to me that line skips efficiently break the function into logical chunks with an obvious intermediate result. It will significantly increase readability.

https://github.com/llvm/llvm-project/pull/233525


More information about the cfe-commits mailing list