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

via cfe-commits cfe-commits at lists.llvm.org
Sat Oct 24 07:15:16 PDT 2026


https://github.com/mark-zilber updated https://github.com/llvm/llvm-project/pull/233525

>From 46a47b302f9f5ec8a9eb5d8febff2d9448771127 Mon Sep 17 00:00:00 2001
From: Mark Zilber <markusbusiness69 at gmail.com>
Date: Sat, 17 Oct 2026 15:36:07 +0400
Subject: [PATCH 1/2] [analyzer] Add alpha.cplusplus.PlacementSelfCopy checker

The checker finds placement new expressions that initialize the new
object from the storage it is placed into, e.g. `::new (p) T(*p)`.
This is the `ctordtor.PlacementSelfCopy` entry of the potential
checkers list.

When a constructor of the new object receives a reference to the
target storage, the checker records the writes into that storage and
reports a read of a part that has already been overwritten. Reads
through `this` are allowed. CSA binds an initializer list only after
evaluating all of its elements, so for `::new (p) Agg{...}` and array
placement new the elements preceding the current one are treated as
written.

A copy or move constructor that is not inlined (a trivial copy, a
constructor without a visible definition, or one that exceeded the
analysis budget) is reported whenever its source is the target
storage.

Reports for `std::construct_at` would end inside the standard library
and be suppressed, so they are emitted at the call site.

Tested on abseil-cpp, protobuf and parts of LLVM (about 1400
translation units) with no reports.

Part of #233519
---
 clang/docs/ReleaseNotes.md                    |   6 +
 clang/docs/analyzer/checkers.md               |  49 ++
 .../clang/StaticAnalyzer/Checkers/Checkers.td |   5 +
 .../StaticAnalyzer/Checkers/CMakeLists.txt    |   1 +
 .../Checkers/PlacementSelfCopyChecker.cpp     | 438 ++++++++++++++++++
 .../Inputs/system-header-simulator-cxx.h      |   6 +
 clang/test/Analysis/placement-self-copy.cpp   | 252 ++++++++++
 7 files changed, 757 insertions(+)
 create mode 100644 clang/lib/StaticAnalyzer/Checkers/PlacementSelfCopyChecker.cpp
 create mode 100644 clang/test/Analysis/placement-self-copy.cpp

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 9e5d92cae727cc..0b8ea41e3a3826 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -1255,6 +1255,12 @@ features cannot lower the translation-unit ABI level;
 %   - Moved checkers
 %   - Diagnostic changes
 
+#### New checkers and features
+
+- Added the ``alpha.cplusplus.PlacementSelfCopy`` checker, which reports
+  placement new expressions and ``std::construct_at`` calls that initialize the
+  new object from the storage it is placed into, e.g. ``::new (p) T(*p)``.
+
 #### Improvements
 
 - The lock-order-reversal check in ``alpha.unix.PthreadLock`` is now disabled by default.
diff --git a/clang/docs/analyzer/checkers.md b/clang/docs/analyzer/checkers.md
index e2bbf52943bb87..319b368ed7fa82 100644
--- a/clang/docs/analyzer/checkers.md
+++ b/clang/docs/analyzer/checkers.md
@@ -3503,6 +3503,55 @@ void bad_insert3(std::vector &v1, std::vector &v2) {
 }
 ```
 
+(alpha-cplusplus-placementselfcopy)=
+
+#### alpha.cplusplus.PlacementSelfCopy (C++)
+
+Check for placement new expressions that initialize the new object from the
+storage it is placed into, e.g. `::new (p) T(*p)`. Construction overwrites that
+storage, so a read of a part that has already been written sees the new value
+of the object being created.
+
+If the constructor is inlined, the checker follows its writes to the storage
+and reports the first read of an overwritten part. Reads through `this` are
+allowed. The elements of an initializer list are treated as written in order.
+A copy or move constructor that is not inlined, such as a trivial copy or a
+constructor without a visible definition, is reported whenever its source is
+the storage itself. Calls of `std::construct_at` are reported at the call site.
+
+```cpp
+struct Buffer {
+  int *Data;
+  int Size;
+  Buffer(const Buffer &O) : Data(new int[O.Size]), Size(O.Size) {
+    for (int I = 0; I < Size; ++I)
+      Data[I] = O.Data[I]; // warn: placement new reads a part of its storage
+                           //       that it has already overwritten
+  }
+};
+
+void reconstruct(Buffer *B) {
+  ::new (B) Buffer(*B);
+}
+
+struct Point {
+  int X, Y;
+};
+
+void swap_coordinates(Point *P) {
+  ::new (P) Point{P->Y, P->X}; // warn: P->X is read after X is initialized
+}
+
+struct Trivial {
+  int X;
+};
+
+void trivial_copy(Trivial *T) {
+  ::new (T) Trivial(*T); // warn: object constructed by placement new is
+                         //       initialized from the storage it is placed into
+}
+```
+
 (alpha-cplusplus-smartptr)=
 
 #### alpha.cplusplus.SmartPtr (C++)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 9905649801e85b..ce71e6e501d6b1 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -813,6 +813,11 @@ def MismatchedIteratorChecker : Checker<"MismatchedIterator">,
   Dependencies<[IteratorModeling]>,
   Documentation<HasDocumentation>;
 
+def PlacementSelfCopyChecker : Checker<"PlacementSelfCopy">,
+  HelpText<"Check for placement new expressions that initialize the new "
+           "object from the storage it is placed into">,
+  Documentation<HasDocumentation>;
+
 def SmartPtrChecker: Checker<"SmartPtr">,
   HelpText<"Find the dereference of null SmrtPtr">,
   Dependencies<[SmartPtrModeling]>,
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index f136bd5434113b..a1c2c604020214 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -91,6 +91,7 @@ add_clang_library(clangStaticAnalyzerCheckers
   OpaqueSTLFunctionsModeling.cpp
   OSObjectCStyleCast.cpp
   PaddingChecker.cpp
+  PlacementSelfCopyChecker.cpp
   PointerArithChecker.cpp
   PointerSubChecker.cpp
   PthreadLockChecker.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/PlacementSelfCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PlacementSelfCopyChecker.cpp
new file mode 100644
index 00000000000000..5b06836018ae13
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/PlacementSelfCopyChecker.cpp
@@ -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());
+  if (!CD || !CD->isCopyOrMoveConstructor())
+    return;
+  const MemRegion *This = Ctor->getCXXThisVal().getAsRegion();
+  const MemRegion *Source = Ctor->getArgSVal(0).getAsRegion();
+  if (!This || !Source)
+    return;
+  This = This->StripCasts();
+  Source = Source->StripCasts();
+
+  ProgramStateRef State = C.getState();
+  const Stmt *S = Ctor->getOriginExpr();
+  for (const auto &[Key, Target] : State->get<PlacementTargets>()) {
+    const MemRegion *Dest = getDestination(Key, Target, *Ctor, This, C);
+    if (!Dest)
+      continue;
+    // Inlined constructors are checked per read unless built in a temporary.
+    if (C.wasInlined && isWithin(This, Target, C.getASTContext()))
+      return;
+    if (!overlaps(Source, Dest, C.getASTContext()))
+      return;
+    if (C.getSourceManager().isInSystemHeader(S->getBeginLoc()))
+      C.addTransition(State->set<PendingSelfCopy>(Target));
+    else
+      reportSelfCopy(Ctor->getArgExpr(0), C);
+    return;
+  }
+}
+
+void PlacementSelfCopyChecker::checkPreCall(const CallEvent &Call,
+                                            CheckerContext &C) const {
+  const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call);
+  if (!Ctor)
+    return;
+  ProgramStateRef State = C.getState();
+  if (State->get<PlacementTargets>().isEmpty())
+    return;
+  ArrayRef<ParmVarDecl *> Params = Ctor->parameters();
+  unsigned NumArgs = std::min<size_t>(Ctor->getNumArgs(), Params.size());
+  for (unsigned I = 0; I != NumArgs; ++I) {
+    if (!Params[I]->getType()->isReferenceType())
+      continue;
+    if (const MemRegion *Source = Ctor->getArgSVal(I).getAsRegion()) {
+      checkOverwrittenRead(Ctor->getOriginExpr(), Ctor->getArgExpr(I), Source,
+                           C);
+      if (C.isDifferent())
+        return;
+    }
+  }
+
+  const MemRegion *This = Ctor->getCXXThisVal().getAsRegion();
+  if (!This)
+    return;
+  This = This->StripCasts();
+  for (const auto &[Key, Target] : State->get<PlacementTargets>()) {
+    const MemRegion *Dest = getDestination(Key, Target, *Ctor, This, C);
+    if (!Dest)
+      continue;
+    for (unsigned I = 0; I != NumArgs; ++I) {
+      const MemRegion *Source = Ctor->getArgSVal(I).getAsRegion();
+      if (Params[I]->getType()->isReferenceType() && Source &&
+          overlaps(Source->StripCasts(), Dest, C.getASTContext())) {
+        C.addTransition(State->add<SelfSourcedTargets>(Target));
+        return;
+      }
+    }
+    return;
+  }
+}
+
+void PlacementSelfCopyChecker::checkBind(SVal Loc, SVal Val, const Stmt *S,
+                                         bool AtDeclInit,
+                                         CheckerContext &C) const {
+  const MemRegion *R = Loc.getAsRegion();
+  if (!R)
+    return;
+  R = R->StripCasts();
+  ProgramStateRef State = C.getState();
+  if (!findSelfSourcedTarget(State, R, C) ||
+      State->contains<OverwrittenParts>(R))
+    return;
+  const NoteTag *Note =
+      C.getNoteTag([this, R](PathSensitiveBugReport &BR) -> std::string {
+        if (&BR.getBugType() != &ReadBT ||
+            BR.getErrorNode()->getState()->get<ReadOverwritten>() != R)
+          return "";
+        return "Part of the storage is overwritten here";
+      });
+  C.addTransition(State->add<OverwrittenParts>(R), Note);
+}
+
+void PlacementSelfCopyChecker::checkLocation(SVal Loc, bool IsLoad,
+                                             const Stmt *S,
+                                             CheckerContext &C) const {
+  if (!IsLoad || C.getState()->get<PlacementTargets>().isEmpty())
+    return;
+  const auto *E = dyn_cast_if_present<Expr>(S);
+  const MemRegion *R = Loc.getAsRegion();
+  if (E && R)
+    checkOverwrittenRead(S, E, R, C);
+}
+
+void PlacementSelfCopyChecker::checkPostStmt(const CXXNewExpr *NE,
+                                             CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  PlacementKey Key{NE, C.getStackFrame()};
+  const MemRegion *const *Target = State->get<PlacementTargets>(Key);
+  if (!Target)
+    return;
+  State = State->remove<PlacementTargets>(Key);
+  State = State->remove<SelfSourcedTargets>(*Target);
+  for (const MemRegion *W : State->get<OverwrittenParts>())
+    if (isWithin(W, *Target, C.getASTContext()))
+      State = State->remove<OverwrittenParts>(W);
+  C.addTransition(State);
+}
+
+void ento::registerPlacementSelfCopyChecker(CheckerManager &Mgr) {
+  Mgr.registerChecker<PlacementSelfCopyChecker>();
+}
+
+bool ento::shouldRegisterPlacementSelfCopyChecker(const CheckerManager &) {
+  return true;
+}
diff --git a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
index 78fe4994b8d44f..6a077b57f54c84 100644
--- a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
+++ b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h
@@ -1194,6 +1194,12 @@ void* operator new[] (std::size_t size, void* ptr) throw() { return ptr; };
 void operator delete (void* ptr, void*) throw() {};
 void operator delete[] (void* ptr, void*) throw() {};
 
+namespace std {
+template <class T, class... Args> T *construct_at(T *p, Args &&...args) {
+  return ::new (static_cast<void *>(p)) T(static_cast<Args &&>(args)...);
+}
+}
+
 namespace __cxxabiv1 {
 extern "C" {
 extern char *__cxa_demangle(const char *mangled_name,
diff --git a/clang/test/Analysis/placement-self-copy.cpp b/clang/test/Analysis/placement-self-copy.cpp
new file mode 100644
index 00000000000000..826c709e577535
--- /dev/null
+++ b/clang/test/Analysis/placement-self-copy.cpp
@@ -0,0 +1,252 @@
+// RUN: %clang_analyze_cc1 -std=c++20 %s \
+// RUN:   -analyzer-checker=core,alpha.cplusplus.PlacementSelfCopy \
+// RUN:   -analyzer-output=text -verify -triple x86_64-unknown-linux-gnu
+
+#include "Inputs/system-header-simulator-cxx.h"
+
+struct Trivial {
+  int X;
+};
+
+struct NonTrivial {
+  int *P;
+  NonTrivial(const NonTrivial &Other);
+  NonTrivial(NonTrivial &&Other);
+};
+
+void copy_from_itself_trivial(Trivial *Dst) {
+  ::new (Dst) Trivial(*Dst); // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
+}
+
+void copy_from_itself(NonTrivial *Dst) {
+  ::new (Dst) NonTrivial(*Dst); // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
+}
+
+void move_from_itself(NonTrivial *Dst) {
+  ::new (Dst) NonTrivial(static_cast<NonTrivial &&>(*Dst)); // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
+}
+
+void copy_through_alias(NonTrivial *Dst) {
+  NonTrivial *Alias = Dst; // expected-note{{'Alias' initialized here}}
+  ::new (Dst) NonTrivial(*Alias); // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
+}
+
+void copy_from_local(const NonTrivial &Src) {
+  NonTrivial Obj(Src); // expected-note{{'Obj' initialized here}}
+  ::new (&Obj) NonTrivial(Obj); // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
+}
+
+struct Outer {
+  Trivial Inner;
+};
+
+void copy_from_enclosing_object(Outer *O) {
+  ::new (&O->Inner) Trivial(O->Inner); // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
+}
+
+void copy_from_other(NonTrivial *Dst, NonTrivial *Src) {
+  ::new (Dst) NonTrivial(*Src); // no-warning
+}
+
+struct Pair {
+  Trivial A;
+  Trivial B;
+};
+
+void copy_from_sibling_field(Pair *P) {
+  ::new (&P->A) Trivial(P->B); // no-warning
+}
+
+struct Node {
+  Node *Next;
+  explicit Node(Node *N) : Next(N) {}
+};
+
+void self_referential_pointer(Node *Storage) {
+  ::new (Storage) Node(Storage); // no-warning
+}
+
+void regular_new(NonTrivial *Src) {
+  NonTrivial *P = new NonTrivial(*Src); // no-warning
+  (void)P;
+}
+
+struct Deep {
+  int *P;
+  int N;
+  Deep(const Deep &O) : P(new int[O.N]), N(O.N) { P[0] = O.P[0]; } // expected-note{{Part of the storage is overwritten here}} expected-warning{{Placement new reads a part of its storage that it has already overwritten}} expected-note{{Placement new reads a part of its storage that it has already overwritten}}
+};
+
+void deep_copy_reads_overwritten_pointer(Deep *Dst) {
+  ::new (Dst) Deep(*Dst); // expected-note{{Calling copy constructor for 'Deep'}}
+}
+
+struct Swapped {
+  int X, Y;
+  Swapped(const Swapped &O) : X(O.Y), Y(O.X) {} // expected-note{{Part of the storage is overwritten here}} expected-warning{{Placement new reads a part of its storage that it has already overwritten}} expected-note{{Placement new reads a part of its storage that it has already overwritten}}
+};
+
+void swapped_copy(Swapped *Dst) {
+  ::new (Dst) Swapped(*Dst); // expected-note{{Calling copy constructor for 'Swapped'}}
+}
+
+struct Memberwise {
+  int X, Y;
+  Memberwise(const Memberwise &O) : X(O.X), Y(O.Y) {}
+};
+
+void memberwise_copy(Memberwise *Dst) {
+  ::new (Dst) Memberwise(*Dst); // no-warning
+}
+
+struct ReadsOwnField {
+  int X, Y;
+  ReadsOwnField(const ReadsOwnField &O) : X(O.X), Y(X + 1) {}
+};
+
+void reads_own_field(ReadsOwnField *Dst) {
+  ::new (Dst) ReadsOwnField(*Dst); // no-warning
+}
+
+struct IgnoresSource {
+  int Field;
+  IgnoresSource(IgnoresSource &) { Field = 0; }
+};
+
+void ignores_source(IgnoresSource *Dst) {
+  ::new (Dst) IgnoresSource(*Dst); // no-warning
+}
+
+struct FromInt {
+  int Field;
+  explicit FromInt(int &V) : Field(V + 1) {}
+};
+
+void reads_before_writing(FromInt *Dst) {
+  ::new (Dst) FromInt(Dst->Field); // no-warning
+}
+
+struct HoldsTrivial {
+  Trivial T;
+  int K;
+  HoldsTrivial(const HoldsTrivial &O) : T(O.T), K(O.K) {}
+};
+
+void member_copied_before_overwrite(HoldsTrivial *Dst) {
+  ::new (Dst) HoldsTrivial(*Dst); // no-warning
+}
+
+void construct_at_trivial(Trivial *Dst) {
+  std::construct_at(Dst, *Dst); // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
+}
+
+struct SwappedInStdlib {
+  int X, Y;
+  SwappedInStdlib(const SwappedInStdlib &O) : X(O.Y), Y(O.X) {} // expected-note{{Part of the storage is overwritten here}} expected-warning{{Placement new reads a part of its storage that it has already overwritten}} expected-note{{Placement new reads a part of its storage that it has already overwritten}}
+};
+
+void construct_at_swapped(SwappedInStdlib *Dst) {
+  std::construct_at(Dst, *Dst); // expected-note{{Calling 'construct_at<SwappedInStdlib, SwappedInStdlib &>'}}
+  // expected-note at Inputs/system-header-simulator-cxx.h:* {{Calling copy constructor for 'SwappedInStdlib'}}
+}
+
+void construct_at_other(Trivial *Dst, Trivial *Src) {
+  std::construct_at(Dst, *Src); // no-warning
+}
+
+void array_trivial(Trivial *Dst) {
+  ::new (Dst) Trivial[1]{*Dst}; // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
+}
+
+void array_deep(Deep *Dst) {
+  ::new (Dst) Deep[1]{*Dst}; // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
+}
+
+void array_second_element(Trivial *Dst) {
+  ::new (Dst) Trivial[2]{Dst[1], Dst[0]}; // expected-warning{{Placement new reads a part of its storage that it has already overwritten}} expected-note{{Placement new reads a part of its storage that it has already overwritten}}
+}
+
+void array_other(Trivial *Dst, Trivial *Src) {
+  ::new (Dst) Trivial[1]{*Src}; // no-warning
+}
+
+struct Agg {
+  int X;
+  int Y;
+};
+
+void aggregate_swapped(Agg *Dst) {
+  ::new (Dst) Agg{Dst->Y, Dst->X}; // expected-warning{{Placement new reads a part of its storage that it has already overwritten}} expected-note{{Placement new reads a part of its storage that it has already overwritten}}
+}
+
+void aggregate_same_order(Agg *Dst) {
+  ::new (Dst) Agg{Dst->X, Dst->Y}; // no-warning
+}
+
+struct Nested {
+  Agg Inner;
+  int Z;
+};
+
+void nested_aggregate(Nested *Dst) {
+  ::new (Dst) Nested{{Dst->Z, Dst->Inner.X}, 0}; // expected-warning{{Placement new reads a part of its storage that it has already overwritten}} expected-note{{Placement new reads a part of its storage that it has already overwritten}}
+}
+
+void aggregate_copy(Agg *Dst) {
+  ::new (Dst) Agg{*Dst}; // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
+}
+
+struct ChainedReset {
+  int *Begin, *End;
+  ChainedReset(const ChainedReset &) { Begin = End = nullptr; }
+};
+
+void chained_assignment_in_self_copy(ChainedReset *Dst) {
+  ::new (Dst) ChainedReset(*Dst); // no-warning
+}
+
+struct Stream {
+  int *Cur, *End;
+  explicit Stream(int &Source) { Cur = End = nullptr; (void)Source; }
+};
+
+void chained_assignment_from_other(Stream *Dst, int &Other) {
+  ::new (Dst) Stream(Other); // no-warning
+}
+
+struct Ends {
+  int Values[2];
+  int &get(int I) { return Values[I]; }
+  explicit Ends(const int (&Init)[2]) : Values{Init[0], Init[1]} {
+    get(0) += 1;
+  }
+};
+
+void member_call_from_other(Ends *Dst, const int (&Init)[2]) {
+  ::new (Dst) Ends(Init); // no-warning
+}
+
+struct Recursive;
+void place(Recursive *Dst, const Recursive &Src, Recursive *Scratch,
+           const Recursive *Other, int Depth);
+
+struct Recursive {
+  int X, Y;
+  Recursive(const Recursive &O, Recursive *Scratch, const Recursive *Other,
+            int Depth)
+      : X(O.Y) { // expected-note{{Part of the storage is overwritten here}}
+    if (Depth) // expected-note{{'Depth' is 1}} expected-note{{Taking true branch}}
+      place(Scratch, *Other, nullptr, nullptr, 0);
+    Y = O.X; // expected-warning{{Placement new reads a part of its storage that it has already overwritten}} expected-note{{Placement new reads a part of its storage that it has already overwritten}}
+  }
+};
+
+void place(Recursive *Dst, const Recursive &Src, Recursive *Scratch,
+           const Recursive *Other, int Depth) {
+  ::new (Dst) Recursive(Src, Scratch, Other, Depth); // expected-note{{Calling constructor for 'Recursive'}}
+}
+
+void same_new_expression_in_nested_frame(Recursive *Dst, Recursive *Scratch,
+                                         const Recursive *Other) {
+  place(Dst, *Dst, Scratch, Other, 1); // expected-note{{Calling 'place'}}
+}

>From f6852cd1d997d432fb5caed80e0997e086323193 Mon Sep 17 00:00:00 2001
From: Mark Zilber <markusbusiness69 at gmail.com>
Date: Sat, 24 Oct 2026 17:59:38 +0400
Subject: [PATCH 2/2] [analyzer] PlacementSelfCopy: track the old object during
 construction

Report any access to the target storage through the old object while the
new object is being constructed. Such values are unspecified by
[class.cdtor]/2 whatever order the constructor copies in, so a memberwise
copy is reported as well. Accesses through `this` are allowed.

The checker follows the old object through the parameters, local and
global variables bound to it, `this` of the methods called on it, and
pointers derived from it by arithmetic, std::move, std::addressof,
conditional operators or methods that return a pointer into it.

An access in the new-initializer itself is reported too. A call that is
not inlined and gets the old object by reference or as its object
argument is reported, unless the argument only contains the target, e.g.
a pool with an inline buffer. A lambda that captures the old object is
reported when it is created.

Other fixes:
- the target is limited to the placed object and is taken from the
  placement argument, so an operator new declared without a body works
  too;
- a region of unknown size overlaps another one only if one lies within
  the other, and a symbolic region gets its size from the pointee type;
- base subobjects keep their own extent, and empty records are skipped;
- remove two redundant dyn_cast_if_present calls.

Add tests with CRTP, templates and an operator new without a body.
---
 clang/docs/analyzer/checkers.md               |  58 +-
 .../Checkers/PlacementSelfCopyChecker.cpp     | 565 +++++++++++-------
 clang/test/Analysis/placement-self-copy.cpp   | 565 +++++++++++++++---
 3 files changed, 861 insertions(+), 327 deletions(-)

diff --git a/clang/docs/analyzer/checkers.md b/clang/docs/analyzer/checkers.md
index 319b368ed7fa82..b139470f47cb74 100644
--- a/clang/docs/analyzer/checkers.md
+++ b/clang/docs/analyzer/checkers.md
@@ -3507,41 +3507,69 @@ void bad_insert3(std::vector &v1, std::vector &v2) {
 
 #### alpha.cplusplus.PlacementSelfCopy (C++)
 
-Check for placement new expressions that initialize the new object from the
-storage it is placed into, e.g. `::new (p) T(*p)`. Construction overwrites that
-storage, so a read of a part that has already been written sees the new value
-of the object being created.
-
-If the constructor is inlined, the checker follows its writes to the storage
-and reports the first read of an overwritten part. Reads through `this` are
-allowed. The elements of an initializer list are treated as written in order.
-A copy or move constructor that is not inlined, such as a trivial copy or a
-constructor without a visible definition, is reported whenever its source is
-the storage itself. Calls of `std::construct_at` are reported at the call site.
+Check for placement new expressions that access the storage they are placed
+into through the old object while the new object is being constructed, e.g.
+`::new (p) T(*p)`. A value read during the construction through a glvalue not
+obtained from `this` is unspecified ([class.cdtor]/2); writes through such a
+glvalue are reported as well. Calls of
+`std::construct_at` are checked as well; a report that would end inside the
+standard library is emitted at the call site.
+
+The checker follows the reference or pointer that received the old storage
+through variables, pointer arithmetic, the functions it is passed to and the
+methods called on it, and reports the first of these operations on it:
+
+- a read or a write of the storage;
+- passing it to a function or calling its method that is not inlined, except
+  for calls that only return their argument, such as `std::move`,
+  `std::forward`, `std::as_const` and `std::addressof`;
+- capturing it in a lambda, unless a pointer to it is captured.
+
+Taking, storing, comparing and shifting its address, accessing members without
+reading them and calling inlined methods that read nothing are allowed, and so
+are all accesses through `this` or its copies. An object that only contains the
+storage, such as a pool with an inline buffer, is not the old object; only its
+accesses that overlap the storage are reported.
 
 ```cpp
 struct Buffer {
   int *Data;
   int Size;
-  Buffer(const Buffer &O) : Data(new int[O.Size]), Size(O.Size) {
+  Buffer(const Buffer &O)
+      : Data(new int[O.Size]), // warn: placement new accesses the storage it
+                               //       is placed into during construction of
+                               //       the new object
+        Size(O.Size) {
     for (int I = 0; I < Size; ++I)
-      Data[I] = O.Data[I]; // warn: placement new reads a part of its storage
-                           //       that it has already overwritten
+      Data[I] = O.Data[I];
   }
 };
 
 void reconstruct(Buffer *B) {
   ::new (B) Buffer(*B);
 }
+```
+
+An access of the old object in the new-initializer itself is reported as well,
+whatever the order of the elements.
 
+```cpp
 struct Point {
   int X, Y;
 };
 
 void swap_coordinates(Point *P) {
-  ::new (P) Point{P->Y, P->X}; // warn: P->X is read after X is initialized
+  ::new (P) Point{P->Y, P->X}; // warn: placement new accesses the storage it
+                               //       is placed into during construction of
+                               //       the new object
 }
+```
+
+When placement new itself calls a copy or move constructor that is not inlined,
+such as a trivial copy or a constructor without a visible definition, and its
+source is in the storage, it is reported as a self-copy.
 
+```cpp
 struct Trivial {
   int X;
 };
diff --git a/clang/lib/StaticAnalyzer/Checkers/PlacementSelfCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PlacementSelfCopyChecker.cpp
index 5b06836018ae13..cc0da471642f3d 100644
--- a/clang/lib/StaticAnalyzer/Checkers/PlacementSelfCopyChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/PlacementSelfCopyChecker.cpp
@@ -6,15 +6,17 @@
 //
 //===----------------------------------------------------------------------===//
 //
-// 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.
+// Defines a checker that finds placement new expressions that access the
+// target storage through the old object while the new object is being
+// constructed, e.g. `::new (p) T(*p)`. A call that is not inlined is assumed
+// to access the old object passed to it; when it is the copy or move
+// constructor called by placement new itself, it is reported as a self-copy.
 //
 //===----------------------------------------------------------------------===//
 
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/ParentMap.h"
+#include "clang/Basic/Builtins.h"
 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
 #include "clang/StaticAnalyzer/Core/Checker.h"
@@ -28,26 +30,26 @@ 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 *)
+// Variables that refer to the old storage, mapped to the placement target.
+REGISTER_MAP_WITH_PROGRAMSTATE(SourceVars, const MemRegion *, const MemRegion *)
+// Frames of methods called on the old storage, mapped to the placement target.
+REGISTER_MAP_WITH_PROGRAMSTATE(SourceFrames, const StackFrame *,
+                               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>> {
+                     check::Location, check::PostStmt<LambdaExpr>,
+                     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};
+  const BugType AccessBT{this, "Placement new accesses its own storage",
+                         categories::MemoryError};
 
-  void reportRead(const Expr *E, const MemRegion *Overwritten,
-                  CheckerContext &C) const;
+  void reportAccess(const Expr *E, 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;
@@ -56,6 +58,7 @@ class PlacementSelfCopyChecker
                  CheckerContext &C) const;
   void checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
                      CheckerContext &C) const;
+  void checkPostStmt(const LambdaExpr *LE, CheckerContext &C) const;
   void checkPostStmt(const CXXNewExpr *NE, CheckerContext &C) const;
 };
 } // namespace
@@ -68,191 +71,270 @@ struct Extent {
 };
 } // namespace
 
+// Base class subobjects keep their own extent.
+static const MemRegion *stripCasts(const MemRegion *R) {
+  return R->StripCasts(/*StripBaseAndDerivedCasts=*/false);
+}
+
 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);
-  }
+  QualType Ty;
+  if (const auto *TR = dyn_cast<TypedValueRegion>(R))
+    Ty = TR->getValueType();
+  else if (const auto *SR = dyn_cast<SymbolicRegion>(R))
+    Ty = SR->getPointeeStaticType();
+  if (!Ty.isNull() && !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;
+// Returns the extents of A and B if both are known within the same base.
+static std::optional<std::pair<Extent, Extent>>
+getKnownExtents(const MemRegion *A, const MemRegion *B, ASTContext &Ctx) {
   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);
+  if (!EA || !EB || !EA->End || !EB->End || EA->Base != EB->Base)
+    return std::nullopt;
+  return std::make_pair(*EA, *EB);
+}
+
+static bool overlaps(const MemRegion *A, const MemRegion *B, ASTContext &Ctx) {
+  if (auto E = getKnownExtents(A, B, Ctx))
+    return E->first.Begin < *E->second.End && E->second.Begin < *E->first.End;
+  A = stripCasts(A);
+  return A == stripCasts(B) || A->isSubRegionOf(B) || B->isSubRegionOf(A);
 }
 
 static bool isWithin(const MemRegion *R, const MemRegion *Container,
                      ASTContext &Ctx) {
-  if (R == Container || R->isSubRegionOf(Container))
+  if (auto E = getKnownExtents(R, Container, Ctx))
+    return E->second.Begin <= E->first.Begin && *E->first.End <= *E->second.End;
+  R = stripCasts(R);
+  return R == stripCasts(Container) || R->isSubRegionOf(Container);
+}
+
+// Limits R to the placed object instead of the whole buffer it lives in.
+static const MemRegion *getPlacedRegion(const CXXNewExpr *NE,
+                                        const MemRegion *R, CheckerContext &C) {
+  ASTContext &Ctx = C.getASTContext();
+  QualType Ty = NE->getAllocatedType();
+  if (NE->isArray()) {
+    std::optional<const Expr *> Size = NE->getArraySize();
+    std::optional<llvm::APSInt> N =
+        Size && *Size ? (*Size)->getIntegerConstantExpr(Ctx) : std::nullopt;
+    if (!N)
+      return R;
+    Ty =
+        Ctx.getConstantArrayType(Ty, *N, nullptr, ArraySizeModifier::Normal, 0);
+  }
+  const auto *SR = dyn_cast<SubRegion>(R);
+  const auto *TR = dyn_cast<TypedValueRegion>(R);
+  if (!SR || (TR && Ctx.hasSameUnqualifiedType(TR->getValueType(), Ty)))
+    return R;
+  return C.getStoreManager().getRegionManager().getElementRegion(
+      Ty, C.getSValBuilder().makeArrayIndex(0), SR, Ctx);
+}
+
+// Calls that return their argument or its address without accessing it, e.g.
+// `std::move(X)`, `std::addressof(X)` or `&X` with an overloaded `operator&`.
+static bool isPassThroughCall(const CallExpr *CE) {
+  if (!CE || CE->getNumArgs() != 1)
+    return false;
+  if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(CE))
+    return OCE->getOperator() == OO_Amp;
+  const FunctionDecl *FD = CE->getDirectCallee();
+  switch (FD ? FD->getBuiltinID() : 0) {
+  case Builtin::BIaddressof:
+  case Builtin::BI__addressof:
+  case Builtin::BI__builtin_addressof:
+  case Builtin::BIas_const:
+  case Builtin::BIforward:
+  case Builtin::BIforward_like:
+  case Builtin::BImove:
+  case Builtin::BImove_if_noexcept:
     return true;
-  std::optional<Extent> ER = getExtent(R, Ctx), EC = getExtent(Container, Ctx);
-  if (!ER || !EC || ER->Base != EC->Base || ER->Begin < EC->Begin)
+  default:
     return false;
-  return !EC->End || (ER->End && *ER->End <= *EC->End);
+  }
 }
 
-static bool isThisBased(const Expr *E) {
+// Collects the variables that E accesses memory through, e.g. `O` for
+// `O->X[1]`, `O.A + 1` or `O.data()`, and both `A` and `B` for
+// `(C ? A : B).X`.
+static void collectBaseVars(const Expr *E,
+                            SmallVectorImpl<const VarDecl *> &Vars,
+                            bool &ViaThis) {
   while (E) {
     E = E->IgnoreParenCasts();
-    if (isa<CXXThisExpr>(E))
-      return true;
-    if (const auto *ME = dyn_cast<MemberExpr>(E))
+    if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+      if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+        Vars.push_back(VD);
+      return;
+    }
+    if (isa<CXXThisExpr>(E)) {
+      ViaThis = true;
+      return;
+    }
+    if (const auto *CO = dyn_cast<AbstractConditionalOperator>(E)) {
+      collectBaseVars(CO->getTrueExpr(), Vars, ViaThis);
+      E = CO->getFalseExpr();
+    } else 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 (BO && BO->isAdditiveOp())
+      E = BO->getLHS()->getType()->isPointerType() ? BO->getLHS()
+                                                   : BO->getRHS();
     else if (const auto *UO = dyn_cast<UnaryOperator>(E);
-             UO && UO->getOpcode() == UO_Deref)
+             UO &&
+             (UO->getOpcode() == UO_Deref || UO->getOpcode() == UO_AddrOf ||
+              UO->isIncrementDecrementOp()))
       E = UO->getSubExpr();
+    else if (const auto *CE = dyn_cast<CallExpr>(E); isPassThroughCall(CE))
+      E = CE->getArg(0);
+    else if (const auto *MCE = dyn_cast<CXXMemberCallExpr>(E))
+      E = MCE->getImplicitObjectArgument();
     else
-      return false;
+      return;
   }
+}
+
+static bool isEmptyRecord(const MemRegion *R) {
+  const auto *TR = dyn_cast<TypedValueRegion>(R);
+  const CXXRecordDecl *RD =
+      TR ? TR->getValueType()->getAsCXXRecordDecl() : nullptr;
+  return RD && RD->hasDefinition() && RD->isEmpty();
+}
+
+static bool isInInitializer(const CXXNewExpr *NE, const Stmt *S,
+                            CheckerContext &C) {
+  const Expr *Init = NE->getInitializer();
+  const ParentMap &PM = C.getCurrentAnalysisDeclContext()->getParentMap();
+  for (const Stmt *P = S; P && P != NE; P = PM.getParent(P))
+    if (P == Init)
+      return true;
   return false;
 }
 
-static const MemRegion *findSelfSourcedTarget(ProgramStateRef State,
-                                              const MemRegion *R,
+// Returns the target whose new-initializer S uses Source in.
+static const MemRegion *findInitializedTarget(const Stmt *S,
+                                              const MemRegion *Source,
                                               CheckerContext &C) {
-  for (const MemRegion *Target : State->get<SelfSourcedTargets>())
-    if (isWithin(R, Target, C.getASTContext()))
+  if (isEmptyRecord(Source))
+    return nullptr;
+  for (const auto &[Key, Target] : C.getState()->get<PlacementTargets>())
+    if (Key.second == C.getStackFrame() && isInInitializer(Key.first, S, C) &&
+        overlaps(Source, 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;
+// Captured variables and `this` in a lambda body belong to the frame of the
+// function that defines the lambda. Only pointers and `this` get here: other
+// captures of the old object are reported when the lambda is created.
+static const StackFrame *getOwningFrame(const VarDecl *VD,
+                                        const StackFrame *Frame) {
+  while (Frame) {
+    const auto *MD = dyn_cast_if_present<CXXMethodDecl>(Frame->getDecl());
+    if (!MD || !MD->getParent()->isLambda() ||
+        (VD && VD->getDeclContext() == MD))
+      return Frame;
+    const auto *Outer = dyn_cast<Decl>(MD->getParent()->getDeclContext());
+    do
+      Frame = Frame->getParent();
+    while (Frame && Frame->getDecl() != Outer);
+  }
   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())
+// Returns the target that E accesses through a variable that refers to it,
+// or through `this` of a method called on the old storage.
+static const MemRegion *findSourceTarget(const Expr *E, const MemRegion *Source,
+                                         CheckerContext &C) {
+  if (isEmptyRecord(Source))
     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;
+  SmallVector<const VarDecl *, 2> Vars;
+  bool ViaThis = false;
+  collectBaseVars(E, Vars, ViaThis);
+  ProgramStateRef State = C.getState();
+  auto Matches = [&](const MemRegion *const *Target) {
+    return Target && overlaps(Source, *Target, C.getASTContext());
+  };
+  for (const VarDecl *VD : Vars) {
+    const StackFrame *Owner = getOwningFrame(VD, C.getStackFrame());
+    const MemRegion *Var =
+        Owner ? State->getLValue(VD, Owner).getAsRegion() : nullptr;
+    if (const MemRegion *const *T = Var ? State->get<SourceVars>(Var) : nullptr;
+        Matches(T))
+      return *T;
   }
-  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 StackFrame *Owner =
+      ViaThis ? getOwningFrame(nullptr, C.getStackFrame()) : nullptr;
+  if (const MemRegion *const *T =
+          Owner ? State->get<SourceFrames>(Owner) : nullptr;
+      Matches(T))
+    return *T;
+  return nullptr;
+}
 
-    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;
+// Returns the target that E refers to in call S, either through the old
+// storage or directly in the new-initializer.
+static const MemRegion *findTarget(const Expr *E, const MemRegion *Source,
+                                   const Stmt *S, CheckerContext &C) {
+  if (const MemRegion *Target = findSourceTarget(E, Source, C))
+    return Target;
+  return findInitializedTarget(S, Source, C);
 }
 
-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);
+// Returns whether E refers to a part of the old storage, not to an object that
+// merely contains it, such as a pool with an inline buffer.
+static bool refersToOldObject(const Expr *E, const MemRegion *Source,
+                              const Stmt *S, CheckerContext &C) {
+  const MemRegion *Target = findTarget(E, Source, S, C);
+  if (!Target || !isWithin(Source, Target, C.getASTContext()))
+    return false;
+  // An enclosing object of the same size, e.g. `O->Inner` for a target `Inner`.
+  return stripCasts(Target) == stripCasts(Source) ||
+         !Target->isSubRegionOf(Source);
 }
 
-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);
+// Returns the argument that passes the old object to a call that is not
+// inlined. Such a call may access it in any way.
+static const Expr *findOpaqueSourceUse(const CallEvent &Call,
+                                       CheckerContext &C) {
+  if (isPassThroughCall(dyn_cast_if_present<CallExpr>(Call.getOriginExpr())))
+    return nullptr;
+  if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
+    const Expr *Obj = IC->getCXXThisExpr();
+    const MemRegion *Source = IC->getCXXThisVal().getAsRegion();
+    if (Obj && Source &&
+        refersToOldObject(Obj, Source, Call.getOriginExpr(), C))
+      return Obj;
+  }
+  for (unsigned I = 0, E = Call.getNumArgs(); I != E; ++I) {
+    const MemRegion *Source = Call.getArgSVal(I).getAsRegion();
+    const Expr *Arg = Call.getArgExpr(I);
+    if (Source && Arg &&
+        refersToOldObject(Arg, Source, Call.getOriginExpr(), C))
+      return Arg;
   }
-  if (Overwritten)
-    reportRead(E, Overwritten, C);
+  return nullptr;
 }
 
-void PlacementSelfCopyChecker::reportRead(const Expr *E,
-                                          const MemRegion *Overwritten,
-                                          CheckerContext &C) const {
-  ExplodedNode *N =
-      C.generateErrorNode(C.getState()->set<ReadOverwritten>(Overwritten));
+void PlacementSelfCopyChecker::reportAccess(const Expr *E,
+                                            CheckerContext &C) const {
+  ExplodedNode *N = C.generateErrorNode();
   if (!N)
     return;
   auto R = std::make_unique<PathSensitiveBugReport>(
-      ReadBT,
-      "Placement new reads a part of its storage that it has already "
-      "overwritten",
+      AccessBT,
+      "Placement new accesses the storage it is placed into during "
+      "construction of the new object",
       N);
   R->addRange(E->getSourceRange());
   C.emitReport(std::move(R));
@@ -277,14 +359,14 @@ void PlacementSelfCopyChecker::reportSelfCopy(const Expr *Arg,
 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());
+  const Expr *Origin = 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())) {
+        overlaps(Arg, Target, C.getASTContext())) {
       reportSelfCopy(Call.getArgExpr(I), C);
       return;
     }
@@ -299,119 +381,138 @@ void PlacementSelfCopyChecker::checkPostCall(const CallEvent &Call,
     const FunctionDecl *OperatorNew = NE->getOperatorNew();
     if (!OperatorNew || !OperatorNew->isReservedGlobalPlacementOperator())
       return;
-    const MemRegion *Target = Call.getReturnValue().getAsRegion();
+    // The operator may be declared without a body.
+    const MemRegion *Target =
+        Alloc->getArgSVal(Alloc->getNumImplicitArgs()).getAsRegion();
     if (!Target)
       return;
-    C.addTransition(C.getState()->set<PlacementTargets>({NE, C.getStackFrame()},
-                                                        Target->StripCasts()));
+    Target = getPlacedRegion(NE, stripCasts(Target), C);
+    C.addTransition(
+        C.getState()->set<PlacementTargets>({NE, C.getStackFrame()}, Target));
     return;
   }
 
+  // Inlined calls are checked per read.
   const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call);
-  if (!Ctor) {
-    reportPendingSelfCopy(Call, C);
-    return;
-  }
-  const auto *CD = dyn_cast_if_present<CXXConstructorDecl>(Ctor->getDecl());
-  if (!CD || !CD->isCopyOrMoveConstructor())
-    return;
-  const MemRegion *This = Ctor->getCXXThisVal().getAsRegion();
-  const MemRegion *Source = Ctor->getArgSVal(0).getAsRegion();
-  if (!This || !Source)
-    return;
-  This = This->StripCasts();
-  Source = Source->StripCasts();
-
-  ProgramStateRef State = C.getState();
-  const Stmt *S = Ctor->getOriginExpr();
-  for (const auto &[Key, Target] : State->get<PlacementTargets>()) {
-    const MemRegion *Dest = getDestination(Key, Target, *Ctor, This, C);
-    if (!Dest)
-      continue;
-    // Inlined constructors are checked per read unless built in a temporary.
-    if (C.wasInlined && isWithin(This, Target, C.getASTContext()))
+  if (!C.wasInlined && !C.getState()->get<PlacementTargets>().isEmpty()) {
+    const CXXConstructorDecl *CD = Ctor ? Ctor->getDecl() : nullptr;
+    const MemRegion *Source = CD && CD->isCopyOrMoveConstructor()
+                                  ? Ctor->getArgSVal(0).getAsRegion()
+                                  : nullptr;
+    if (const MemRegion *Target =
+            Source ? findInitializedTarget(Ctor->getOriginExpr(), Source, C)
+                   : nullptr) {
+      if (C.getSourceManager().isInSystemHeader(
+              Ctor->getOriginExpr()->getBeginLoc()))
+        C.addTransition(C.getState()->set<PendingSelfCopy>(Target));
+      else
+        reportSelfCopy(Ctor->getArgExpr(0), C);
       return;
-    if (!overlaps(Source, Dest, C.getASTContext()))
+    }
+    if (const Expr *E = findOpaqueSourceUse(Call, C)) {
+      reportAccess(E, C);
       return;
-    if (C.getSourceManager().isInSystemHeader(S->getBeginLoc()))
-      C.addTransition(State->set<PendingSelfCopy>(Target));
-    else
-      reportSelfCopy(Ctor->getArgExpr(0), C);
-    return;
+    }
   }
+  if (!Ctor)
+    reportPendingSelfCopy(Call, C);
 }
 
 void PlacementSelfCopyChecker::checkPreCall(const CallEvent &Call,
                                             CheckerContext &C) const {
-  const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call);
-  if (!Ctor)
-    return;
   ProgramStateRef State = C.getState();
   if (State->get<PlacementTargets>().isEmpty())
     return;
-  ArrayRef<ParmVarDecl *> Params = Ctor->parameters();
-  unsigned NumArgs = std::min<size_t>(Ctor->getNumArgs(), Params.size());
-  for (unsigned I = 0; I != NumArgs; ++I) {
-    if (!Params[I]->getType()->isReferenceType())
-      continue;
-    if (const MemRegion *Source = Ctor->getArgSVal(I).getAsRegion()) {
-      checkOverwrittenRead(Ctor->getOriginExpr(), Ctor->getArgExpr(I), Source,
-                           C);
-      if (C.isDifferent())
-        return;
-    }
+  if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) {
+    const Expr *Obj = IC->getCXXThisExpr();
+    const MemRegion *Source = IC->getCXXThisVal().getAsRegion();
+    const MemRegion *Target =
+        Obj && Source ? findTarget(Obj, Source, Call.getOriginExpr(), C)
+                      : nullptr;
+    if (const StackFrame *Frame =
+            Target ? Call.getCalleeStackFrame(C.blockCount()) : nullptr)
+      State = State->set<SourceFrames>(Frame, Target);
   }
-
-  const MemRegion *This = Ctor->getCXXThisVal().getAsRegion();
-  if (!This)
-    return;
-  This = This->StripCasts();
-  for (const auto &[Key, Target] : State->get<PlacementTargets>()) {
-    const MemRegion *Dest = getDestination(Key, Target, *Ctor, This, C);
-    if (!Dest)
+  ArrayRef<ParmVarDecl *> Params = Call.parameters();
+  unsigned NumArgs = std::min<size_t>(Call.getNumArgs(), Params.size());
+  for (unsigned I = 0; I != NumArgs; ++I) {
+    QualType ParamTy = Params[I]->getType();
+    const MemRegion *Source = Call.getArgSVal(I).getAsRegion();
+    const Expr *Arg = Call.getArgExpr(I);
+    if ((!ParamTy->isReferenceType() && !ParamTy->isPointerType()) || !Source ||
+        !Arg)
       continue;
-    for (unsigned I = 0; I != NumArgs; ++I) {
-      const MemRegion *Source = Ctor->getArgSVal(I).getAsRegion();
-      if (Params[I]->getType()->isReferenceType() && Source &&
-          overlaps(Source->StripCasts(), Dest, C.getASTContext())) {
-        C.addTransition(State->add<SelfSourcedTargets>(Target));
-        return;
-      }
-    }
-    return;
+    const MemRegion *Target = findTarget(Arg, Source, Call.getOriginExpr(), C);
+    if (const ParamVarRegion *Param =
+            Target ? Call.getParameterLocation(I, C.blockCount()) : nullptr)
+      State = State->set<SourceVars>(Param, Target);
   }
+  C.addTransition(State);
 }
 
 void PlacementSelfCopyChecker::checkBind(SVal Loc, SVal Val, const Stmt *S,
                                          bool AtDeclInit,
                                          CheckerContext &C) const {
-  const MemRegion *R = Loc.getAsRegion();
-  if (!R)
-    return;
-  R = R->StripCasts();
   ProgramStateRef State = C.getState();
-  if (!findSelfSourcedTarget(State, R, C) ||
-      State->contains<OverwrittenParts>(R))
+  const auto *Var = dyn_cast_or_null<VarRegion>(Loc.getAsRegion());
+  if (!Var || State->get<PlacementTargets>().isEmpty())
+    return;
+  QualType Ty = Var->getDecl()->getType();
+  if (!Ty->isReferenceType() && !Ty->isPointerType())
     return;
-  const NoteTag *Note =
-      C.getNoteTag([this, R](PathSensitiveBugReport &BR) -> std::string {
-        if (&BR.getBugType() != &ReadBT ||
-            BR.getErrorNode()->getState()->get<ReadOverwritten>() != R)
-          return "";
-        return "Part of the storage is overwritten here";
-      });
-  C.addTransition(State->add<OverwrittenParts>(R), Note);
+  const Expr *Init = nullptr;
+  if (AtDeclInit)
+    Init = Var->getDecl()->getInit();
+  else if (const auto *BO = dyn_cast_if_present<BinaryOperator>(S);
+           BO && BO->getOpcode() == BO_Assign)
+    Init = BO->getRHS();
+  else
+    Init = dyn_cast_if_present<Expr>(S);
+  const MemRegion *Source = Val.getAsRegion();
+  const MemRegion *Target =
+      Init && Source ? findSourceTarget(Init, Source, C) : nullptr;
+  if (Target)
+    C.addTransition(State->set<SourceVars>(Var, Target));
+  else if (State->contains<SourceVars>(Var))
+    C.addTransition(State->remove<SourceVars>(Var));
 }
 
 void PlacementSelfCopyChecker::checkLocation(SVal Loc, bool IsLoad,
                                              const Stmt *S,
                                              CheckerContext &C) const {
-  if (!IsLoad || C.getState()->get<PlacementTargets>().isEmpty())
+  if (C.getState()->get<PlacementTargets>().isEmpty())
     return;
   const auto *E = dyn_cast_if_present<Expr>(S);
   const MemRegion *R = Loc.getAsRegion();
-  if (E && R)
-    checkOverwrittenRead(S, E, R, C);
+  if (!E || !R)
+    return;
+  if (findTarget(E, R, S, C))
+    reportAccess(E, C);
+}
+
+// A lambda that captures the old object reads or modifies it.
+void PlacementSelfCopyChecker::checkPostStmt(const LambdaExpr *LE,
+                                             CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  if (State->get<SourceVars>().isEmpty())
+    return;
+  for (const LambdaCapture &Capture : LE->captures()) {
+    const auto *VD = Capture.capturesVariable()
+                         ? dyn_cast<VarDecl>(Capture.getCapturedVar())
+                         : nullptr;
+    if (!VD || VD->getType()->isPointerType())
+      continue;
+    const MemRegion *Var =
+        State->getLValue(VD, C.getStackFrame()).getAsRegion();
+    const MemRegion *const *Target =
+        Var ? State->get<SourceVars>(Var) : nullptr;
+    const MemRegion *Source =
+        Target ? State->getSVal(Var).getAsRegion() : nullptr;
+    if (Source && isWithin(Source, *Target, C.getASTContext())) {
+      reportAccess(LE, C);
+      return;
+    }
+  }
 }
 
 void PlacementSelfCopyChecker::checkPostStmt(const CXXNewExpr *NE,
@@ -422,10 +523,12 @@ void PlacementSelfCopyChecker::checkPostStmt(const CXXNewExpr *NE,
   if (!Target)
     return;
   State = State->remove<PlacementTargets>(Key);
-  State = State->remove<SelfSourcedTargets>(*Target);
-  for (const MemRegion *W : State->get<OverwrittenParts>())
-    if (isWithin(W, *Target, C.getASTContext()))
-      State = State->remove<OverwrittenParts>(W);
+  for (const auto &[Var, VarTarget] : State->get<SourceVars>())
+    if (VarTarget == *Target)
+      State = State->remove<SourceVars>(Var);
+  for (const auto &[Frame, FrameTarget] : State->get<SourceFrames>())
+    if (FrameTarget == *Target)
+      State = State->remove<SourceFrames>(Frame);
   C.addTransition(State);
 }
 
diff --git a/clang/test/Analysis/placement-self-copy.cpp b/clang/test/Analysis/placement-self-copy.cpp
index 826c709e577535..0cc36bbd451d4c 100644
--- a/clang/test/Analysis/placement-self-copy.cpp
+++ b/clang/test/Analysis/placement-self-copy.cpp
@@ -1,6 +1,26 @@
 // RUN: %clang_analyze_cc1 -std=c++20 %s \
 // RUN:   -analyzer-checker=core,alpha.cplusplus.PlacementSelfCopy \
 // RUN:   -analyzer-output=text -verify -triple x86_64-unknown-linux-gnu
+// RUN: %clang_analyze_cc1 -std=c++20 %s -DDECLARED_PLACEMENT_NEW \
+// RUN:   -analyzer-checker=core,alpha.cplusplus.PlacementSelfCopy \
+// RUN:   -verify=declared -triple x86_64-unknown-linux-gnu
+
+#ifdef DECLARED_PLACEMENT_NEW
+
+// The reserved placement operator has no body, so its return value is not the
+// placement argument.
+void *operator new(__SIZE_TYPE__, void *P) noexcept;
+
+struct Swapped {
+  int X, Y;
+  Swapped(const Swapped &O) : X(O.Y), Y(O.X) {} // declared-warning{{Placement new accesses the storage it is placed into during construction of the new object}}
+};
+
+void swapped_copy(Swapped *Dst) {
+  ::new (Dst) Swapped(*Dst);
+}
+
+#else
 
 #include "Inputs/system-header-simulator-cxx.h"
 
@@ -48,6 +68,18 @@ void copy_from_other(NonTrivial *Dst, NonTrivial *Src) {
   ::new (Dst) NonTrivial(*Src); // no-warning
 }
 
+void copy_to_previous_element(Trivial *P) {
+  ::new (P - 1) Trivial(*P); // no-warning
+}
+
+void copy_to_next_element(NonTrivial *P) {
+  ::new (P + 1) NonTrivial(*P); // no-warning
+}
+
+void copy_to_partially_overlapping_bytes(Trivial *P) {
+  ::new ((char *)P + 2) Trivial(*P); // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
+}
+
 struct Pair {
   Trivial A;
   Trivial B;
@@ -71,69 +103,59 @@ void regular_new(NonTrivial *Src) {
   (void)P;
 }
 
-struct Deep {
-  int *P;
-  int N;
-  Deep(const Deep &O) : P(new int[O.N]), N(O.N) { P[0] = O.P[0]; } // expected-note{{Part of the storage is overwritten here}} expected-warning{{Placement new reads a part of its storage that it has already overwritten}} expected-note{{Placement new reads a part of its storage that it has already overwritten}}
-};
-
-void deep_copy_reads_overwritten_pointer(Deep *Dst) {
-  ::new (Dst) Deep(*Dst); // expected-note{{Calling copy constructor for 'Deep'}}
-}
-
-struct Swapped {
+struct Memberwise {
   int X, Y;
-  Swapped(const Swapped &O) : X(O.Y), Y(O.X) {} // expected-note{{Part of the storage is overwritten here}} expected-warning{{Placement new reads a part of its storage that it has already overwritten}} expected-note{{Placement new reads a part of its storage that it has already overwritten}}
+  Memberwise(const Memberwise &O) : X(O.X), Y(O.Y) {} // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
 };
 
-void swapped_copy(Swapped *Dst) {
-  ::new (Dst) Swapped(*Dst); // expected-note{{Calling copy constructor for 'Swapped'}}
+void memberwise_copy(Memberwise *Dst) {
+  ::new (Dst) Memberwise(*Dst); // expected-note{{Calling copy constructor for 'Memberwise'}}
 }
 
-struct Memberwise {
-  int X, Y;
-  Memberwise(const Memberwise &O) : X(O.X), Y(O.Y) {}
+struct IgnoresSource {
+  int Field;
+  IgnoresSource(IgnoresSource &) { Field = 0; }
 };
 
-void memberwise_copy(Memberwise *Dst) {
-  ::new (Dst) Memberwise(*Dst); // no-warning
+void ignores_source(IgnoresSource *Dst) {
+  ::new (Dst) IgnoresSource(*Dst); // no-warning
 }
 
 struct ReadsOwnField {
   int X, Y;
-  ReadsOwnField(const ReadsOwnField &O) : X(O.X), Y(X + 1) {}
+  ReadsOwnField(const ReadsOwnField &) : X(1), Y(X + 1) {}
 };
 
 void reads_own_field(ReadsOwnField *Dst) {
   ::new (Dst) ReadsOwnField(*Dst); // no-warning
 }
 
-struct IgnoresSource {
-  int Field;
-  IgnoresSource(IgnoresSource &) { Field = 0; }
+struct ChainedReset {
+  int *Begin, *End;
+  ChainedReset(const ChainedReset &) { Begin = End = nullptr; }
 };
 
-void ignores_source(IgnoresSource *Dst) {
-  ::new (Dst) IgnoresSource(*Dst); // no-warning
+void chained_assignment_in_self_copy(ChainedReset *Dst) {
+  ::new (Dst) ChainedReset(*Dst); // no-warning
 }
 
-struct FromInt {
-  int Field;
-  explicit FromInt(int &V) : Field(V + 1) {}
+struct Ends {
+  int Values[2];
+  int &get(int I) { return Values[I]; }
+  Ends(const Ends &) : Values{1, 2} { get(0) += 1; }
 };
 
-void reads_before_writing(FromInt *Dst) {
-  ::new (Dst) FromInt(Dst->Field); // no-warning
+void member_call_on_this_in_self_copy(Ends *Dst) {
+  ::new (Dst) Ends(*Dst); // no-warning
 }
 
-struct HoldsTrivial {
-  Trivial T;
-  int K;
-  HoldsTrivial(const HoldsTrivial &O) : T(O.T), K(O.K) {}
+struct FromInt {
+  int Field;
+  explicit FromInt(int &V) : Field(V + 1) {} // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
 };
 
-void member_copied_before_overwrite(HoldsTrivial *Dst) {
-  ::new (Dst) HoldsTrivial(*Dst); // no-warning
+void reads_member_by_reference(FromInt *Dst) {
+  ::new (Dst) FromInt(Dst->Field); // expected-note{{Calling constructor for 'FromInt'}}
 }
 
 void construct_at_trivial(Trivial *Dst) {
@@ -142,7 +164,7 @@ void construct_at_trivial(Trivial *Dst) {
 
 struct SwappedInStdlib {
   int X, Y;
-  SwappedInStdlib(const SwappedInStdlib &O) : X(O.Y), Y(O.X) {} // expected-note{{Part of the storage is overwritten here}} expected-warning{{Placement new reads a part of its storage that it has already overwritten}} expected-note{{Placement new reads a part of its storage that it has already overwritten}}
+  SwappedInStdlib(const SwappedInStdlib &O) : X(O.Y), Y(O.X) {} // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
 };
 
 void construct_at_swapped(SwappedInStdlib *Dst) {
@@ -158,12 +180,8 @@ void array_trivial(Trivial *Dst) {
   ::new (Dst) Trivial[1]{*Dst}; // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
 }
 
-void array_deep(Deep *Dst) {
-  ::new (Dst) Deep[1]{*Dst}; // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
-}
-
 void array_second_element(Trivial *Dst) {
-  ::new (Dst) Trivial[2]{Dst[1], Dst[0]}; // expected-warning{{Placement new reads a part of its storage that it has already overwritten}} expected-note{{Placement new reads a part of its storage that it has already overwritten}}
+  ::new (Dst) Trivial[2]{Dst[1], Dst[0]}; // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
 }
 
 void array_other(Trivial *Dst, Trivial *Src) {
@@ -175,12 +193,8 @@ struct Agg {
   int Y;
 };
 
-void aggregate_swapped(Agg *Dst) {
-  ::new (Dst) Agg{Dst->Y, Dst->X}; // expected-warning{{Placement new reads a part of its storage that it has already overwritten}} expected-note{{Placement new reads a part of its storage that it has already overwritten}}
-}
-
 void aggregate_same_order(Agg *Dst) {
-  ::new (Dst) Agg{Dst->X, Dst->Y}; // no-warning
+  ::new (Dst) Agg{Dst->X, Dst->Y}; // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
 }
 
 struct Nested {
@@ -189,43 +203,13 @@ struct Nested {
 };
 
 void nested_aggregate(Nested *Dst) {
-  ::new (Dst) Nested{{Dst->Z, Dst->Inner.X}, 0}; // expected-warning{{Placement new reads a part of its storage that it has already overwritten}} expected-note{{Placement new reads a part of its storage that it has already overwritten}}
+  ::new (Dst) Nested{{Dst->Z, Dst->Inner.X}, 0}; // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
 }
 
 void aggregate_copy(Agg *Dst) {
   ::new (Dst) Agg{*Dst}; // expected-warning{{Object constructed by placement new is initialized from the storage it is placed into}} expected-note{{Object constructed by placement new is initialized from the storage it is placed into}}
 }
 
-struct ChainedReset {
-  int *Begin, *End;
-  ChainedReset(const ChainedReset &) { Begin = End = nullptr; }
-};
-
-void chained_assignment_in_self_copy(ChainedReset *Dst) {
-  ::new (Dst) ChainedReset(*Dst); // no-warning
-}
-
-struct Stream {
-  int *Cur, *End;
-  explicit Stream(int &Source) { Cur = End = nullptr; (void)Source; }
-};
-
-void chained_assignment_from_other(Stream *Dst, int &Other) {
-  ::new (Dst) Stream(Other); // no-warning
-}
-
-struct Ends {
-  int Values[2];
-  int &get(int I) { return Values[I]; }
-  explicit Ends(const int (&Init)[2]) : Values{Init[0], Init[1]} {
-    get(0) += 1;
-  }
-};
-
-void member_call_from_other(Ends *Dst, const int (&Init)[2]) {
-  ::new (Dst) Ends(Init); // no-warning
-}
-
 struct Recursive;
 void place(Recursive *Dst, const Recursive &Src, Recursive *Scratch,
            const Recursive *Other, int Depth);
@@ -233,11 +217,11 @@ void place(Recursive *Dst, const Recursive &Src, Recursive *Scratch,
 struct Recursive {
   int X, Y;
   Recursive(const Recursive &O, Recursive *Scratch, const Recursive *Other,
-            int Depth)
-      : X(O.Y) { // expected-note{{Part of the storage is overwritten here}}
+            int Depth) {
     if (Depth) // expected-note{{'Depth' is 1}} expected-note{{Taking true branch}}
       place(Scratch, *Other, nullptr, nullptr, 0);
-    Y = O.X; // expected-warning{{Placement new reads a part of its storage that it has already overwritten}} expected-note{{Placement new reads a part of its storage that it has already overwritten}}
+    X = O.Y; // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+    Y = O.X;
   }
 };
 
@@ -250,3 +234,422 @@ void same_new_expression_in_nested_frame(Recursive *Dst, Recursive *Scratch,
                                          const Recursive *Other) {
   place(Dst, *Dst, Scratch, Other, 1); // expected-note{{Calling 'place'}}
 }
+
+template <typename Derived> struct ReinitMixin {
+  void reinit(const Derived &Source) {
+    ::new (static_cast<Derived *>(this)) Derived(Source); // expected-note{{Calling implicit copy constructor for 'WithString'}}
+  }
+};
+
+struct WithString : ReinitMixin<WithString> { // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+  int Value;
+  std::string Name;
+  WithString(int V, std::string N) : Value(V), Name(N) {}
+};
+
+void crtp_reinit_from_itself(WithString &Obj) {
+  Obj.reinit(Obj); // expected-note{{Calling 'ReinitMixin::reinit'}}
+}
+
+template <typename T> struct Box {
+  T Value;
+  Box(const Box &O) : Value(O.Value) {} // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+};
+
+void class_template_non_trivial_member(Box<NonTrivial> *Dst) {
+  ::new (Dst) Box<NonTrivial>(*Dst); // expected-note{{Calling copy constructor for 'Box<NonTrivial>'}}
+}
+
+struct Base1 {
+  int A;
+};
+struct Base2 {
+  int B;
+};
+
+struct TwoBases : Base1, Base2 {
+  int X;
+  TwoBases(const TwoBases &O) : Base1(O), Base2(O), X(O.X) {} // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+};
+
+void bases_copied_from_itself(TwoBases *Dst) {
+  ::new (Dst) TwoBases(*Dst); // expected-note{{Calling copy constructor for 'TwoBases'}}
+}
+
+struct Empty {};
+
+struct EmptyBase : Empty {
+  int X;
+  EmptyBase(const EmptyBase &O)
+      : Empty(O),
+        X(O.X) {} // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+};
+
+void empty_base_is_not_read(EmptyBase *Dst) {
+  ::new (Dst) EmptyBase(*Dst); // expected-note{{Calling copy constructor for 'EmptyBase'}}
+}
+
+struct WritesOther {
+  int X;
+  explicit WritesOther(int &Other) {
+    Other = 1; // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+    X = Other;
+  }
+};
+
+void larger_buffer(void *Buf) {
+  ::new (Buf) WritesOther(*(int *)((char *)Buf + 64)); // no-warning
+}
+
+void aliases_the_object(void *Buf) {
+  ::new (Buf) WritesOther(*(int *)Buf); // expected-note{{Calling constructor for 'WritesOther'}}
+}
+
+struct ThisAliasHelper;
+int getX(const ThisAliasHelper &S);
+
+struct ThisAliasHelper {
+  int X, Y;
+  ThisAliasHelper(const ThisAliasHelper &) {
+    X = 1;
+    Y = getX(*this) + 1;
+  }
+};
+
+int getX(const ThisAliasHelper &S) { return S.X; }
+
+void this_passed_to_helper(ThisAliasHelper *Dst) {
+  ::new (Dst) ThisAliasHelper(*Dst); // no-warning
+}
+
+struct LocalThisAlias {
+  int X, Y;
+  LocalThisAlias(const LocalThisAlias &) {
+    X = 1;
+    auto *Self = this;
+    Y = Self->X + 1;
+  }
+};
+
+void this_stored_in_local(LocalThisAlias *Dst) {
+  ::new (Dst) LocalThisAlias(*Dst); // no-warning
+}
+
+struct SourceByPointer {
+  int X, Y;
+  explicit SourceByPointer(const SourceByPointer *O) : X(O->Y), Y(O->X) {} // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+};
+
+void source_by_pointer(SourceByPointer *Dst) {
+  ::new (Dst) SourceByPointer(Dst); // expected-note{{Calling constructor for 'SourceByPointer'}}
+}
+
+struct SourceToHelper;
+int getY(const SourceToHelper &S);
+
+struct SourceToHelper {
+  int X, Y;
+  SourceToHelper(const SourceToHelper &O) {
+    X = 1;
+    Y = getY(O); // expected-note{{Calling 'getY'}}
+  }
+};
+
+int getY(const SourceToHelper &S) { return S.Y; } // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+
+void source_passed_to_helper(SourceToHelper *Dst) {
+  ::new (Dst) SourceToHelper(*Dst); // expected-note{{Calling copy constructor for 'SourceToHelper'}}
+}
+
+struct SourceInLocal {
+  int X, Y;
+  SourceInLocal(const SourceInLocal &O) {
+    X = 1;
+    const SourceInLocal *Src = &O;
+    Y = Src->Y; // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+  }
+};
+
+void source_stored_in_local(SourceInLocal *Dst) {
+  ::new (Dst) SourceInLocal(*Dst); // expected-note{{Calling copy constructor for 'SourceInLocal'}}
+}
+
+struct LocalReassigned {
+  int X, Y;
+  LocalReassigned(const LocalReassigned &O, const LocalReassigned &Other) {
+    const LocalReassigned *P = &O;
+    P = &Other;
+    X = P->Y;
+    Y = 0;
+  }
+};
+
+void local_reassigned_to_other(LocalReassigned *Dst, LocalReassigned *Other) {
+  ::new (Dst) LocalReassigned(*Dst, *Other); // no-warning
+}
+
+struct MethodOnSource {
+  int X, Y;
+  int getX() const { return X; } // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+  MethodOnSource(const MethodOnSource &O) {
+    X = 1;
+    Y = O.getX(); // expected-note{{Calling 'MethodOnSource::getX'}}
+  }
+};
+
+void method_called_on_source(MethodOnSource *Dst) {
+  ::new (Dst) MethodOnSource(*Dst); // expected-note{{Calling copy constructor for 'MethodOnSource'}}
+}
+
+struct LambdaCapture {
+  int X, Y;
+  LambdaCapture(const LambdaCapture &O) {
+    X = 1;
+    Y = [&] { return O.Y; }(); // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+  }
+};
+
+void source_captured_by_lambda(LambdaCapture *Dst) {
+  ::new (Dst) LambdaCapture(*Dst); // expected-note{{Calling copy constructor for 'LambdaCapture'}}
+}
+
+struct LambdaCapturesPointer {
+  int X;
+  const LambdaCapturesPointer *Next;
+  explicit LambdaCapturesPointer(const LambdaCapturesPointer *O) : X(0) {
+    Next = [O] { return O; }();
+  }
+};
+
+void pointer_captured_by_lambda(LambdaCapturesPointer *Dst) {
+  ::new (Dst) LambdaCapturesPointer(Dst); // no-warning
+}
+
+struct Conditional {
+  int X, Y;
+  Conditional(const Conditional &O, const Conditional &Other, bool B) {
+    X = 1;
+    const Conditional &R = B ? O : Other; // expected-note{{'B' is true}} expected-note{{'?' condition is true}}
+    Y = R.Y; // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+  }
+};
+
+void source_chosen_by_conditional(Conditional *Dst, Conditional *Other) {
+  ::new (Dst) Conditional(*Dst, *Other, true); // expected-note{{Calling constructor for 'Conditional'}}
+}
+
+void other_chosen_by_conditional(Conditional *Dst, Conditional *Other) {
+  ::new (Dst) Conditional(*Dst, *Other, false); // no-warning
+}
+
+struct AddressOfSource {
+  int X, Y;
+  AddressOfSource(const AddressOfSource &O) {
+    X = 1;
+    const AddressOfSource *P = __builtin_addressof(O);
+    Y = P->Y; // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+  }
+};
+
+void address_of_source(AddressOfSource *Dst) {
+  ::new (Dst) AddressOfSource(*Dst); // expected-note{{Calling copy constructor for 'AddressOfSource'}}
+}
+
+struct OpaqueMethodOnSource {
+  int X, Y;
+  int get() const;
+  OpaqueMethodOnSource(const OpaqueMethodOnSource &O) {
+    X = 1;
+    Y = O.get(); // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+  }
+};
+
+void opaque_method_called_on_source(OpaqueMethodOnSource *Dst) {
+  ::new (Dst) OpaqueMethodOnSource(*Dst); // expected-note{{Calling copy constructor for 'OpaqueMethodOnSource'}}
+}
+
+struct StoresAddress;
+const StoresAddress *Remembered;
+void remember(const StoresAddress &R) { Remembered = &R; }
+
+struct StoresAddress {
+  int X;
+  StoresAddress(const StoresAddress &O) : X(0) { remember(O); }
+};
+
+void callee_stores_address_of_source(StoresAddress *Dst) {
+  ::new (Dst) StoresAddress(*Dst); // no-warning
+}
+
+struct ReadsStoredAddress;
+const ReadsStoredAddress *RememberedToRead;
+void rememberToRead(const ReadsStoredAddress &R) { RememberedToRead = &R; }
+
+struct ReadsStoredAddress {
+  int X, Y;
+  ReadsStoredAddress(const ReadsStoredAddress &O) {
+    X = 1;
+    rememberToRead(O);
+    Y = RememberedToRead->X; // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+  }
+};
+
+void read_through_stored_address(ReadsStoredAddress *Dst) {
+  ::new (Dst) ReadsStoredAddress(*Dst); // expected-note{{Calling copy constructor for 'ReadsStoredAddress'}}
+}
+
+struct MovedFrom {
+  int X, Y;
+  MovedFrom(MovedFrom &&O) : X(O.Y), Y(O.X) {} // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+};
+
+void move_from_itself_with_std_move(MovedFrom *Dst) {
+  ::new (Dst) MovedFrom(std::move(*Dst)); // expected-note{{Calling move constructor for 'MovedFrom'}}
+}
+
+struct Pool {
+  int Count;
+  alignas(8) char Buf[64];
+  void note();
+  int peek() const { return Buf[0]; } // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+};
+
+struct PoolItem {
+  int V;
+  explicit PoolItem(Pool &P) : V(P.Count) { P.note(); }
+};
+
+PoolItem *pool_method_not_inlined(Pool &P) {
+  return ::new (P.Buf) PoolItem(P); // no-warning
+}
+
+struct PoolItemLambda {
+  int V;
+  explicit PoolItemLambda(Pool &P) : V(0) {
+    [&] { P.note(); }();
+  }
+};
+
+PoolItemLambda *pool_captured_by_lambda(Pool &P) {
+  return ::new (P.Buf) PoolItemLambda(P); // no-warning
+}
+
+struct PoolItemPeek {
+  int V;
+  explicit PoolItemPeek(Pool &P) : V(P.peek()) {} // expected-note{{Calling 'Pool::peek'}}
+};
+
+PoolItemPeek *pool_reads_buffer(Pool &P) {
+  return ::new (P.Buf) PoolItemPeek(P); // expected-note{{Calling constructor for 'PoolItemPeek'}}
+}
+
+struct ArithmeticOnMember {
+  int A[4];
+  int S;
+  ArithmeticOnMember(const ArithmeticOnMember &O) : S(0) {
+    const int *P = O.A + 1;
+    S = *P; // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+  }
+};
+
+void read_after_pointer_arithmetic(ArithmeticOnMember *Dst) {
+  ::new (Dst) ArithmeticOnMember(*Dst); // expected-note{{Calling copy constructor for 'ArithmeticOnMember'}}
+}
+
+struct IncrementsSource {
+  int A[4];
+  int S;
+  IncrementsSource(const IncrementsSource &O) : S(0) {
+    const int *P = O.A;
+    ++P;
+    S = *P; // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+  }
+};
+
+void read_after_increment(IncrementsSource *Dst) {
+  ::new (Dst) IncrementsSource(*Dst); // expected-note{{Calling copy constructor for 'IncrementsSource'}}
+}
+
+struct LambdaDereferencesPointer {
+  int X, Y;
+  explicit LambdaDereferencesPointer(const LambdaDereferencesPointer *O)
+      : X(1) {
+    Y = [O] { return O->Y; }(); // expected-note{{Calling 'operator()'}}
+  } // expected-warning at -1{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note at -1{{Placement new accesses the storage it is placed into during construction of the new object}}
+};
+
+void pointer_dereferenced_in_lambda(LambdaDereferencesPointer *Dst) {
+  ::new (Dst) LambdaDereferencesPointer(Dst); // expected-note{{Calling constructor for 'LambdaDereferencesPointer'}}
+}
+
+extern "C" void *memcpy(void *, const void *, __SIZE_TYPE__);
+
+struct CopiesBytes {
+  int X, Y;
+  CopiesBytes(const CopiesBytes &O) {
+    memcpy((void *)this, &O, sizeof(CopiesBytes)); // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+  }
+};
+
+void memcpy_from_source(CopiesBytes *Dst) {
+  ::new (Dst) CopiesBytes(*Dst); // expected-note{{Calling copy constructor for 'CopiesBytes'}}
+}
+
+struct MemberAddress {
+  int X;
+  int *P;
+  MemberAddress(const MemberAddress &O) : X(1), P(const_cast<int *>(&O.X)) {}
+};
+
+void address_of_member_is_not_read(MemberAddress *Dst) {
+  ::new (Dst) MemberAddress(*Dst); // no-warning
+}
+
+struct ShiftsPointer {
+  ShiftsPointer *Next;
+  explicit ShiftsPointer(ShiftsPointer *N) : Next(N + 1) {}
+};
+
+void pointer_arithmetic_is_not_read(ShiftsPointer *Dst) {
+  ::new (Dst) ShiftsPointer(Dst); // no-warning
+}
+
+struct MethodWithoutRead {
+  int X;
+  int answer() const { return 42; }
+  MethodWithoutRead(const MethodWithoutRead &O) : X(O.answer()) {}
+};
+
+void method_that_reads_nothing(MethodWithoutRead *Dst) {
+  ::new (Dst) MethodWithoutRead(*Dst); // no-warning
+}
+
+struct DataOnSource {
+  int A[4];
+  int S;
+  const int *data() const { return A; }
+  DataOnSource(const DataOnSource &O) : S(0) {
+    const int *P = O.data();
+    S = P[1]; // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}}
+  }
+};
+
+void read_through_returned_pointer(DataOnSource *Dst) {
+  ::new (Dst) DataOnSource(*Dst); // expected-note{{Calling copy constructor for 'DataOnSource'}}
+}
+
+struct ThisInLambda {
+  int X, Y;
+  int get() const {
+    return [this] { return X; }(); // expected-warning{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Placement new accesses the storage it is placed into during construction of the new object}} expected-note{{Calling 'operator()'}}
+  }
+  ThisInLambda(const ThisInLambda &O) : X(1) {
+    Y = O.get(); // expected-note{{Calling 'ThisInLambda::get'}}
+  }
+};
+
+void this_captured_in_method_on_source(ThisInLambda *Dst) {
+  ::new (Dst) ThisInLambda(*Dst); // expected-note{{Calling copy constructor for 'ThisInLambda'}}
+}
+
+#endif



More information about the cfe-commits mailing list