[clang] [analyzer] Add alpha.cplusplus.PlacementSelfCopy checker (PR #233525)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Oct 24 07:53:24 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,
----------------
mark-zilber wrote:
Refactored whole code.
https://github.com/llvm/llvm-project/pull/233525
More information about the cfe-commits
mailing list