llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: mark-zilber

<details>
<summary>Changes</summary>

The checker finds 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)`. This is the `ctordtor.PlacementSelfCopy` entry of the 
[potential checkers 
list](https://clang-analyzer.llvm.org/potential_checkers.html).

During construction, a value read through a glvalue that is not obtained from 
`this` is unspecified ([class.cdtor]/2). The checker marks the parameter that 
receives the old storage in the new-initializer and follows it through the 
variables it is stored in (also through `std::addressof`, `?:` and pointer 
arithmetic), into the functions it is passed to, the methods called on it and 
the lambdas that capture a pointer to it. It reports the first of these:

- a read or a write of the target storage through it, or in the new-initializer 
itself;
- 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` and 
`std::addressof`;
- capturing it in a lambda, unless a pointer to it is captured.

 Taking, storing or shifting its address, accessing a member without reading it 
and calling an inlined method that reads nothing are allowed. Accesses to an 
object that only contains the target storage, such as a pool with an inline 
buffer, are not reported unless they overlap the target. The target is limited 
to the size of the placed type, so the rest of a larger buffer is not part of 
it.

When placement new itself calls 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) and its source is in the target storage, it is 
reported as a self-copy. A report that would end inside the standard library, 
as for `std::construct_at`, is emitted at the call site.

```cpp
struct Buffer {
  int *Data;
  int Size;
  Buffer(const Buffer &amp;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 &lt; Size; ++I)
      Data[I] = O.Data[I];
  }
};

void reconstruct(Buffer *B) {
  ::new (B) Buffer(*B);
}
```

### Testing

- `clang/test/Analysis/placement-self-copy.cpp`: self-copies at the top level, 
in arrays, aggregates and `std::construct_at`; reads in the constructor and in 
the new-initializer; the source followed through pointers, locals, helpers, 
methods, lambdas, `?:`, pointer arithmetic and opaque calls such as `memcpy`; 
class templates and CRTP; base classes. It also covers cases that must stay 
silent: accesses through `this` and its aliases, adjacent objects, a 
constructor that ignores its source, storing the address of the source, members 
whose address is taken without a read, and a pool whose inline buffer holds the 
target. A second run checks a reserved placement `operator new` declared 
without a body.
- All tests in `clang/test/Analysis` pass.
- Real code, `-analyzer-checker=alpha.cplusplus.PlacementSelfCopy` on the 
translation units below:

| Project | Revision | Translation units | Reports |
| --- | --- | --- | --- |
| abseil-cpp (without googletest) | `4cbb3d4` | 426 | 0 |
| protobuf | `522b8d4` | 538 | 0 |
| EASTL | `e323f67` | 126 | 0 |
| ETL | `5fe4a6c` | 394 | 0 |
| godot (without `thirdparty`) | `469659e` | 1516 | 0 |
| LLVM (`llvm/lib/Support`, `llvm/lib/IR`, `llvm/lib/Analysis`, 
`llvm/lib/Transforms/Utils`, `llvm/unittests/ADT`, `llvm/unittests/Support`, 
`clang/lib/AST`, `clang/lib/Sema`) | `43b1971` | 920 | 0 |

One abseil translation unit (`absl/log/log_format_test.cc`) hits an existing 
analyzer assertion in `createTemporaryRegionIfNeeded`. It crashes the same way 
with only the `core` checkers enabled.

### Limitations

- The source is not followed through fields: storing its address in a member 
and reading through it is missed.
- An old object that is larger than the new one cannot be told apart from an 
object that contains the target, so passing it whole to an opaque function is 
missed.
- Init-captures (`[&amp;R = O]`), user-defined placement allocation functions 
and pointers to the storage obtained before the placement new are not followed.
- A function that is not inlined is assumed to access the old object passed to 
it, so one that ignores it is still reported, as is a lambda that captures the 
old object but is never called.
- `B ? O : *this` is treated as the old object even when `this` is chosen.
- Inside a lambda, a captured variable is looked up in the nearest frame of the 
function that defines the lambda, which may be the wrong one when that function 
is recursive.

Part of #<!-- -->233519


---

Patch is 52.89 KiB, truncated to 20.00 KiB below, full version: 
https://github.com/llvm/llvm-project/pull/233525.diff


7 Files Affected:

- (modified) clang/docs/ReleaseNotes.md (+6) 
- (modified) clang/docs/analyzer/checkers.md (+77) 
- (modified) clang/include/clang/StaticAnalyzer/Checkers/Checkers.td (+5) 
- (modified) clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt (+1) 
- (added) clang/lib/StaticAnalyzer/Checkers/PlacementSelfCopyChecker.cpp (+541) 
- (modified) clang/test/Analysis/Inputs/system-header-simulator-cxx.h (+6) 
- (added) clang/test/Analysis/placement-self-copy.cpp (+655) 


``````````diff
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..b139470f47cb74 100644
--- a/clang/docs/analyzer/checkers.md
+++ b/clang/docs/analyzer/checkers.md
@@ -3503,6 +3503,83 @@ void bad_insert3(std::vector &v1, std::vector &v2) {
 }
 ```
 
+(alpha-cplusplus-placementselfcopy)=
+
+#### alpha.cplusplus.PlacementSelfCopy (C++)
+
+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]), // 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];
+  }
+};
+
+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: 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;
+};
+
+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..cc0da471642f3d
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/PlacementSelfCopyChecker.cpp
@@ -0,0 +1,541 @@
+//===- 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 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"
+#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 *)
+// 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 *)
+
+namespace {
+class PlacementSelfCopyChecker
+    : public Checker<check::PostCall, check::PreCall, check::Bind,
+                     check::Location, check::PostStmt<LambdaExpr>,
+                     check::PostStmt<CXXNewExpr>> {
+  const BugType BT{this, "Placement new initialized from its own storage",
+                   categories::MemoryError};
+  const BugType AccessBT{this, "Placement new accesses its own storage",
+                         categories::MemoryError};
+
+  void reportAccess(const Expr *E, CheckerContext &C) const;
+  void reportSelfCopy(const Expr *Arg, CheckerContext &C) const;
+  void reportPendingSelfCopy(const CallEvent &Call, 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 LambdaExpr *LE, 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
+
+// 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};
+  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;
+}
+
+// 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->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 (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;
+  default:
+    return false;
+  }
+}
+
+// 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 (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->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;
+  }
+}
+
+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;
+}
+
+// Returns the target whose new-initializer S uses Source in.
+static const MemRegion *findInitializedTarget(const Stmt *S,
+                                              const MemRegion *Source,
+                                              CheckerContext &C) {
+  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;
+}
+
+// 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;
+}
+
+// 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;
+  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;
+  }
+  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;
+}
+
+// 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);
+}
+
+// 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);
+}
+
+// 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;
+  }
+  return nullptr;
+}
+
+void PlacementSelfCopyChecker::reportAccess(const Expr *E,
+                                            CheckerContext &C) const {
+  ExplodedNode *N = C.generateErrorNode();
+  if (!N)
+    return;
+  auto R = std::make_unique<PathSensitiveBugReport>(
+      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));
+}
+
+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...
[truncated]

``````````

</details>


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

Reply via email to