https://github.com/mark-zilber created 
https://github.com/llvm/llvm-project/pull/237801

The checker finds storage that placement new reuses for an object of a 
different type and that is then destroyed or deleted as the original type. This 
is the `undefbehavior.ObjLocChanges` entry  of the [potential checkers 
list](https://clang-analyzer.llvm.org/potential_checkers.html), renamed 
following the discussion in #233519

The checker records the type that placement new creates in storage holding an 
object with a non-trivial destructor (a variable, a member, an array element or 
memory from a non-placement `new`). It reports:

- a destructor call on that storage whose class differs from the recorded type 
([basic.life]/11);
- a `delete` through a pointer to a different type, unless the recorded type is 
derived from it and its destructor is virtual ([expr.delete]/3).

Placement new of the original type at the same address restores the storage. 
Raw storage, unions and trivially destructible objects are not tracked. 
Recreating a base class subobject (`this->~Base(); new (this) Base(Other);` on 
a derived object) is reported, since a base class subobject is 
potentially-overlapping and  cannot be transparently replaced 
([intro.object]/2, [basic.life]/2). Reports inside `std`, such as 
`std::destroy_at`, are emitted at the call site.

```cpp
class A {};
class B {
public:
  ~B();
};

void test1() {
  B b;
  new (&b) A;
} // warn: destructor of 'B' is called on storage that holds an object of
  //       type 'A'

void test2() {
  B *b = new B;
  new (b) A;
  delete b; // warn: deleting an object of type 'A' through a pointer to 'B'
}
```

### Testing

- `clang/test/Analysis/destroyed-as-wrong-type.cpp`: locals, heap objects, 
members and array elements; explicit destructor calls; `delete` through a base 
class; `std::destroy_at`; base class recreation; destructors and functions 
without a body. Silent cases: restoring the original type, raw storage, unions, 
trivial destructors, virtual destructors.
- All tests in `clang/test/Analysis` pass.
- Real code, `-analyzer-checker=cplusplus.DestroyedAsWrongType`:

| 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 | 12 |

All LLVM reports come from the move assignment of `UniqueFunctionBase` in 
`llvm/ADT/FunctionExtras.h`, which destroys and recreates the base class 
subobject of a `unique_function`

`absl/log/log_format_test.cc` hits an existing assertion 
https://github.com/llvm/llvm-project/pull/202837#issuecomment-6276167235
### Limitations

- A function or destructor that is not inlined is assumed not to restore the 
original type.
- An explicit non-virtual base class destructor call on a derived object is 
reported, although [class.dtor]/16 allows it.
- A `delete` with a destroying `operator delete` is reported
- `delete[]` through a base class with a virtual destructor is not reported.
- `delete` of a trivially destructible type, objects first created in raw 
storage and array elements with a symbolic index are not checked

Part of #233519


>From 07ebad282da420f78ccf679803cdc50b87505fe3 Mon Sep 17 00:00:00 2001
From: Mark Zilber <[email protected]>
Date: Mon, 2 Nov 2026 01:50:42 +0200
Subject: [PATCH] [analyzer] Add cplusplus.DestroyedAsWrongType checker

Reports storage that placement new reuses for an object of a different
type and that is then destroyed or deleted as the original type
([basic.life]/11, [expr.delete]/3). Destructor calls and deletions made
inside the standard library, such as std::destroy_at, are reported at
the call site.

Tested on abseil-cpp, protobuf, EASTL, ETL, godot and parts of LLVM
(about 3900 translation units). The only reports are twelve in LLVM, all
caused by the move assignment of llvm::unique_function, which destroys
and recreates its UniqueFunctionBase base subobject.

Part of #233519
---
 clang/docs/ReleaseNotes.md                    |   6 +
 clang/docs/analyzer/checkers.md               |  40 ++
 .../clang/StaticAnalyzer/Checkers/Checkers.td |   5 +
 .../StaticAnalyzer/Checkers/CMakeLists.txt    |   1 +
 .../Checkers/DestroyedAsWrongTypeChecker.cpp  | 339 +++++++++++++++++
 .../test/Analysis/destroyed-as-wrong-type.cpp | 358 ++++++++++++++++++
 6 files changed, 749 insertions(+)
 create mode 100644 
clang/lib/StaticAnalyzer/Checkers/DestroyedAsWrongTypeChecker.cpp
 create mode 100644 clang/test/Analysis/destroyed-as-wrong-type.cpp

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 9e5d92cae727cc..ba2b2bd311865a 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 ``cplusplus.DestroyedAsWrongType`` checker, which reports storage
+  reused by placement new for an object of a different type that is then
+  destroyed or deleted as the original type.
+
 #### 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..b653fddcb37183 100644
--- a/clang/docs/analyzer/checkers.md
+++ b/clang/docs/analyzer/checkers.md
@@ -457,6 +457,46 @@ void foo() {
 }
 ```
 
+(cplusplus-destroyedaswrongtype)=
+
+#### cplusplus.DestroyedAsWrongType (C++)
+
+Check for storage that is reused by placement new for an object of a different
+type and is then destroyed or deleted as the original type
+([basic.life]/11, [expr.delete]/3). Destructor calls and deletions inside the
+standard library, such as `std::destroy_at`, are reported at the call site.
+
+Deleting or destroying the object through a base class with a virtual
+destructor is allowed. Placement new into raw storage, such as a character
+buffer or memory from `::operator new`, into a union and into an object with a
+trivial destructor is not reported. Members and array elements destroyed by a
+destructor that is not inlined are checked when that destructor is called.
+
+```cpp
+class A {};
+class B {
+public:
+  ~B();
+};
+
+void test1() {
+  B b;
+  new (&b) A;
+} // warn: destructor of 'B' is called on storage that holds an object of
+  //       type 'A'
+
+void test2() {
+  B *b = new B;
+  new (b) A;
+  delete b; // warn: deleting an object of type 'A' through a pointer to 'B'
+}
+```
+
+**Limitations**
+
+A function that is not inlined is assumed not to recreate the original object
+in the storage passed to it. If it does, the destruction is still reported.
+
 (cplusplus-innerpointer)=
 
 #### cplusplus.InnerPointer (C++)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td 
b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 9905649801e85b..14eed2d61ce38a 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -644,6 +644,11 @@ def ArrayDeleteChecker : Checker<"ArrayDelete">,
            "destructed as their base class.">,
   Documentation<HasDocumentation>;
 
+def DestroyedAsWrongTypeChecker : Checker<"DestroyedAsWrongType">,
+  HelpText<"Check for objects destroyed or deleted as a type that their "
+           "storage no longer holds after placement new">,
+  Documentation<HasDocumentation>;
+
 def InnerPointerChecker : Checker<"InnerPointer">,
   HelpText<"Check for inner pointers of C++ containers used after "
            "re/deallocation">,
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt 
b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index f136bd5434113b..c16e667de4c941 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -37,6 +37,7 @@ add_clang_library(clangStaticAnalyzerCheckers
   DebugContainerModeling.cpp
   DebugIteratorModeling.cpp
   DereferenceChecker.cpp
+  DestroyedAsWrongTypeChecker.cpp
   DirectIvarAssignment.cpp
   DivZeroChecker.cpp
   DynamicTypePropagation.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/DestroyedAsWrongTypeChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/DestroyedAsWrongTypeChecker.cpp
new file mode 100644
index 00000000000000..1cb117cfd37125
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/DestroyedAsWrongTypeChecker.cpp
@@ -0,0 +1,339 @@
+//===- DestroyedAsWrongTypeChecker.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 storage reused by placement new for an object
+// of a different type and then destroyed or deleted as the original type
+// (C++ [basic.life]p11, [expr.delete]p3).
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/ExprCXX.h"
+#include "clang/Analysis/CFG.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"
+#include <optional>
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+struct WrongDestruction {
+  QualType Placed;
+  const CXXRecordDecl *DtorClass;
+  const CXXDeleteExpr *Delete;
+
+  bool operator==(const WrongDestruction &X) const {
+    return Placed == X.Placed && DtorClass == X.DtorClass && Delete == 
X.Delete;
+  }
+  void Profile(llvm::FoldingSetNodeID &ID) const {
+    ID.Add(Placed);
+    ID.AddPointer(DtorClass);
+    ID.AddPointer(Delete);
+  }
+};
+} // namespace
+
+REGISTER_MAP_WITH_PROGRAMSTATE(PlacedObjectTypes, const MemRegion *, QualType)
+REGISTER_SET_WITH_PROGRAMSTATE(NewAllocatedObjects, const MemRegion *)
+REGISTER_MAP_WITH_PROGRAMSTATE(PendingDestructions, const MemRegion *,
+                               WrongDestruction)
+
+namespace {
+class DestroyedAsWrongTypeChecker
+    : public Checker<check::PostStmt<CXXNewExpr>, check::PreCall,
+                     check::PostCall, check::DeadSymbols> {
+  const BugType BT{this, "Object destroyed as a type it no longer has",
+                   categories::MemoryError};
+
+  void reportBug(const MemRegion *R, const WrongDestruction &W,
+                 CheckerContext &C) const;
+  void reportOrDefer(const MemRegion *R, const WrongDestruction &W,
+                     CheckerContext &C) const;
+
+public:
+  void checkPostStmt(const CXXNewExpr *NE, CheckerContext &C) const;
+  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
+  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+  void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
+};
+} // namespace
+
+// An array is keyed by its first element, the object its destructor gets.
+static const MemRegion *getStorageRegion(SVal V, CheckerContext &C) {
+  const MemRegion *R = V.getAsRegion();
+  if (!R)
+    return nullptr;
+  R = R->StripCasts();
+  ASTContext &Ctx = C.getASTContext();
+  while (const auto *TR = dyn_cast<TypedValueRegion>(R)) {
+    const ArrayType *AT = Ctx.getAsArrayType(TR->getValueType());
+    if (!AT)
+      break;
+    R = C.getStoreManager().getRegionManager().getElementRegion(
+        AT->getElementType(), C.getSValBuilder().makeZeroArrayIndex(), TR, 
Ctx);
+  }
+  return R;
+}
+
+// Storage obtained as raw memory has no original object to be replaced.
+static bool holdsOriginalObject(ProgramStateRef State, const MemRegion *R) {
+  if (State->contains<NewAllocatedObjects>(R))
+    return true;
+  const auto *TR = dyn_cast<TypedValueRegion>(R);
+  if (!TR)
+    return false;
+  // Placement new into a union starts the lifetime of one of its members.
+  const CXXRecordDecl *RD = TR->getValueType()->getAsCXXRecordDecl();
+  return RD && !RD->isUnion() && RD->hasNonTrivialDestructor();
+}
+
+static bool isSameRecord(QualType T, const CXXRecordDecl *RD) {
+  const CXXRecordDecl *TD = T->getAsCXXRecordDecl();
+  return TD && TD->getCanonicalDecl() == RD->getCanonicalDecl();
+}
+
+static QualType getStorageType(const MemRegion *R) {
+  if (const auto *TR = dyn_cast<TypedValueRegion>(R))
+    return TR->getValueType();
+  if (const auto *SR = dyn_cast<SymbolicRegion>(R))
+    return SR->getPointeeStaticType();
+  return {};
+}
+
+// A field and a byte offset from its parent are different regions.
+static bool isSameLocation(const MemRegion *A, const MemRegion *B) {
+  RegionOffset OA = A->getAsOffset();
+  RegionOffset OB = B->getAsOffset();
+  return OA.isValid() && OB.isValid() && !OA.hasSymbolicOffset() &&
+         !OB.hasSymbolicOffset() && OA.getRegion() == OB.getRegion() &&
+         OA.getOffset() == OB.getOffset();
+}
+
+static ProgramStateRef forgetPlacedTypes(ProgramStateRef State,
+                                         const MemRegion *R) {
+  for (const MemRegion *Key :
+       llvm::make_first_range(State->get<PlacedObjectTypes>()))
+    if (Key == R || Key->isSubRegionOf(R) || isSameLocation(Key, R))
+      State = State->remove<PlacedObjectTypes>(Key);
+  return State;
+}
+
+static SmallVector<const MemRegion *, 1>
+getPlacementStorages(const CXXNewExpr *NE, CheckerContext &C) {
+  SmallVector<const MemRegion *, 1> Storages;
+  for (const Expr *Arg : NE->placement_arguments())
+    if (Arg->getType()->isPointerType())
+      if (const MemRegion *R = getStorageRegion(C.getSVal(Arg), C))
+        Storages.push_back(R);
+  return Storages;
+}
+
+static bool isInStdFrame(CheckerContext &C) {
+  return AnalysisDeclContext::isInStdNamespace(C.getStackFrame()->getDecl());
+}
+
+void DestroyedAsWrongTypeChecker::reportBug(const MemRegion *R,
+                                            const WrongDestruction &W,
+                                            CheckerContext &C) const {
+  ExplodedNode *N = C.generateErrorNode();
+  if (!N)
+    return;
+  const PrintingPolicy &PP = C.getASTContext().getPrintingPolicy();
+  std::string Placed = W.Placed.getAsString(PP);
+  std::string Msg =
+      W.Delete
+          ? "Deleting an object of type '" + Placed +
+                "' through a pointer to '" +
+                W.Delete->getDestroyedType().getUnqualifiedType().getAsString(
+                    PP) +
+                "'"
+          : "Destructor of '" + W.DtorClass->getQualifiedNameAsString() +
+                "' is called on storage that holds an object of type '" +
+                Placed + "'";
+  auto Report = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
+  Report->markInteresting(R);
+  C.emitReport(std::move(Report));
+}
+
+void DestroyedAsWrongTypeChecker::checkPostStmt(const CXXNewExpr *NE,
+                                                CheckerContext &C) const {
+  const FunctionDecl *OperatorNew = NE->getOperatorNew();
+  if (!OperatorNew)
+    return;
+
+  const MemRegion *Result = getStorageRegion(C.getSVal(NE), C);
+  if (!Result)
+    return;
+
+  ProgramStateRef State = C.getState();
+  SmallVector<const MemRegion *, 1> Storages = getPlacementStorages(NE, C);
+  if (OperatorNew->isReservedGlobalPlacementOperator() && Storages.empty())
+    return;
+  // The result may be typed as the new object, so use the storage argument.
+  const MemRegion *R = nullptr;
+  for (const MemRegion *Storage : Storages)
+    if (OperatorNew->isReservedGlobalPlacementOperator() || Storage == Result 
||
+        isSameLocation(Storage, Result))
+      R = Storage;
+  if (!R) {
+    // An operator new that is not inlined may have placed the object there.
+    for (const MemRegion *Storage : Storages)
+      State = forgetPlacedTypes(State, Storage);
+    C.addTransition(State->add<NewAllocatedObjects>(Result));
+    return;
+  }
+
+  if (NE->isArray()) {
+    const auto *ER = dyn_cast<ElementRegion>(R);
+    C.addTransition(forgetPlacedTypes(State, ER ? ER->getSuperRegion() : R));
+    return;
+  }
+  State = forgetPlacedTypes(State, R);
+  if (!holdsOriginalObject(State, R)) {
+    C.addTransition(State);
+    return;
+  }
+
+  QualType Placed = NE->getAllocatedType().getUnqualifiedType();
+  std::string Note = "Object of type '" +
+                     Placed.getAsString(C.getASTContext().getPrintingPolicy()) 
+
+                     "' is created here by placement new";
+  const NoteTag *Tag =
+      C.getNoteTag([this, R, Note](PathSensitiveBugReport &BR) -> std::string {
+        if (&BR.getBugType() != &BT || !BR.isInteresting(R))
+          return "";
+        return Note;
+      });
+  C.addTransition(State->set<PlacedObjectTypes>(R, Placed), Tag);
+}
+
+// Destroying through a base class with a virtual destructor is well-defined.
+static bool isVirtualBaseDestruction(QualType Placed,
+                                     const CXXRecordDecl *Static) {
+  const CXXRecordDecl *PlacedRD = Placed->getAsCXXRecordDecl();
+  if (!PlacedRD || !PlacedRD->isDerivedFrom(Static))
+    return false;
+  const CXXDestructorDecl *Dtor = Static->getDestructor();
+  return Dtor && Dtor->isVirtual();
+}
+
+void DestroyedAsWrongTypeChecker::reportOrDefer(const MemRegion *R,
+                                                const WrongDestruction &W,
+                                                CheckerContext &C) const {
+  // Reports inside the standard library are suppressed; report at the caller.
+  if (isInStdFrame(C)) {
+    C.addTransition(C.getState()->set<PendingDestructions>(R, W));
+    return;
+  }
+  reportBug(R, W, C);
+}
+
+static const MemRegion *getDestroyedRegion(const CallEvent &Call,
+                                           CheckerContext &C) {
+  const auto *Instance = dyn_cast<CXXInstanceCall>(&Call);
+  return Instance ? getStorageRegion(Instance->getCXXThisVal(), C) : nullptr;
+}
+
+static std::optional<WrongDestruction>
+findWrongDestruction(const CallEvent &Call, const CXXDestructorDecl *Dtor,
+                     const MemRegion *R, ProgramStateRef State) {
+  const QualType *Placed = State->get<PlacedObjectTypes>(R);
+  const CXXRecordDecl *DtorClass = Dtor->getParent();
+  if (!Placed || isSameRecord(*Placed, DtorClass))
+    return std::nullopt;
+
+  const auto *Implicit = dyn_cast<CXXDestructorCall>(&Call);
+  std::optional<CFGDeleteDtor> Delete =
+      Implicit ? Implicit->getCFGElementRef()->getAs<CFGDeleteDtor>()
+               : std::nullopt;
+  // Implicit destructors of automatic objects and members are not virtual.
+  bool CallIsVirtual = !Implicit || Delete;
+  if (CallIsVirtual && isVirtualBaseDestruction(*Placed, DtorClass))
+    return std::nullopt;
+  return WrongDestruction{*Placed, DtorClass,
+                          Delete ? Delete->getDeleteExpr() : nullptr};
+}
+
+// A destructor without a body destroys members, and the analyzer then destroys
+// only the last element of an array, out of the checker's sight.
+static std::optional<std::pair<const MemRegion *, WrongDestruction>>
+findHiddenWrongDestruction(const CallEvent &Call, const MemRegion *R,
+                           ProgramStateRef State) {
+  const MemRegion *Scope = R;
+  if (const auto *ER = dyn_cast<ElementRegion>(R);
+      ER && isa<CXXDestructorCall>(Call))
+    Scope = ER->getSuperRegion();
+  for (const auto &[Key, Placed] : State->get<PlacedObjectTypes>()) {
+    QualType StorageType = getStorageType(Key);
+    const CXXRecordDecl *RD =
+        StorageType.isNull() ? nullptr : StorageType->getAsCXXRecordDecl();
+    if (RD && Key != R && Key->isSubRegionOf(Scope) &&
+        !isSameRecord(Placed, RD))
+      return std::make_pair(Key, WrongDestruction{Placed, RD, nullptr});
+  }
+  return std::nullopt;
+}
+
+void DestroyedAsWrongTypeChecker::checkPreCall(const CallEvent &Call,
+                                               CheckerContext &C) const {
+  const auto *Dtor = dyn_cast_if_present<CXXDestructorDecl>(Call.getDecl());
+  if (!Dtor)
+    return;
+
+  const auto *Implicit = dyn_cast<CXXDestructorCall>(&Call);
+  if (Implicit && Implicit->isBaseDestructor())
+    return;
+
+  const MemRegion *R = getDestroyedRegion(Call, C);
+  if (!R)
+    return;
+  ProgramStateRef State = C.getState();
+  if (std::optional<WrongDestruction> W =
+          findWrongDestruction(Call, Dtor, R, State)) {
+    reportOrDefer(R, *W, C);
+    return;
+  }
+  if (Call.getRuntimeDefinition().getDecl())
+    return;
+  if (auto Hidden = findHiddenWrongDestruction(Call, R, State))
+    reportOrDefer(Hidden->first, Hidden->second, C);
+}
+
+void DestroyedAsWrongTypeChecker::checkPostCall(const CallEvent &Call,
+                                                CheckerContext &C) const {
+  PendingDestructionsTy Pending = C.getState()->get<PendingDestructions>();
+  if (Pending.isEmpty() || isInStdFrame(C))
+    return;
+  const auto &[R, W] = *Pending.begin();
+  reportBug(R, W, C);
+}
+
+void DestroyedAsWrongTypeChecker::checkDeadSymbols(SymbolReaper &SR,
+                                                   CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+  for (const MemRegion *R :
+       llvm::make_first_range(State->get<PlacedObjectTypes>()))
+    if (!SR.isLiveRegion(R))
+      State = State->remove<PlacedObjectTypes>(R);
+  for (const MemRegion *R : State->get<NewAllocatedObjects>())
+    if (!SR.isLiveRegion(R))
+      State = State->remove<NewAllocatedObjects>(R);
+  C.addTransition(State);
+}
+
+void ento::registerDestroyedAsWrongTypeChecker(CheckerManager &Mgr) {
+  Mgr.registerChecker<DestroyedAsWrongTypeChecker>();
+}
+
+bool ento::shouldRegisterDestroyedAsWrongTypeChecker(const CheckerManager &) {
+  return true;
+}
diff --git a/clang/test/Analysis/destroyed-as-wrong-type.cpp 
b/clang/test/Analysis/destroyed-as-wrong-type.cpp
new file mode 100644
index 00000000000000..0b7a22c25392b0
--- /dev/null
+++ b/clang/test/Analysis/destroyed-as-wrong-type.cpp
@@ -0,0 +1,358 @@
+// RUN: %clang_analyze_cc1 -std=c++11 %s \
+// RUN:   -analyzer-checker=core,cplusplus.DestroyedAsWrongType \
+// RUN:   -analyzer-output=text -verify -triple x86_64-unknown-linux-gnu
+
+#include "Inputs/system-header-simulator-cxx.h"
+
+struct A {};
+
+struct B {
+  ~B();
+};
+
+void consume(void *);
+
+void replace_local() {
+  B b;
+  new (&b) A; // expected-note{{Object of type 'A' is created here by 
placement new}}
+} // expected-warning{{Destructor of 'B' is called on storage that holds an 
object of type 'A'}} expected-note{{Destructor of 'B' is called on storage that 
holds an object of type 'A'}}
+
+void replace_heap() {
+  B *b = new B;
+  new (b) A; // expected-note{{Object of type 'A' is created here by placement 
new}}
+  delete b; // expected-warning{{Deleting an object of type 'A' through a 
pointer to 'B'}} expected-note{{Deleting an object of type 'A' through a 
pointer to 'B'}}
+}
+
+struct Base {
+  ~Base();
+};
+struct Derived : Base {};
+
+void delete_through_base_without_virtual_dtor() {
+  Base *p = new Base;
+  new (p) Derived; // expected-note{{Object of type 'Derived' is created here 
by placement new}}
+  delete p; // expected-warning{{Deleting an object of type 'Derived' through 
a pointer to 'Base'}} expected-note{{Deleting an object of type 'Derived' 
through a pointer to 'Base'}}
+}
+
+struct Holder { // expected-warning{{Destructor of 'B' is called on storage 
that holds an object of type 'A'}} expected-note{{Destructor of 'B' is called 
on storage that holds an object of type 'A'}}
+  B Member;
+};
+
+void replace_member() {
+  Holder h;
+  new (&h.Member) A; // expected-note{{Object of type 'A' is created here by 
placement new}}
+} // expected-note{{Calling implicit destructor for 'Holder'}}
+
+void destroy_then_replace() {
+  B b;
+  b.~B();
+  new (&b) A; // expected-note{{Object of type 'A' is created here by 
placement new}}
+} // expected-warning{{Destructor of 'B' is called on storage that holds an 
object of type 'A'}} expected-note{{Destructor of 'B' is called on storage that 
holds an object of type 'A'}}
+
+void explicit_destructor_on_replaced_storage() {
+  B b;
+  new (&b) A; // expected-note{{Object of type 'A' is created here by 
placement new}}
+  b.~B(); // expected-warning{{Destructor of 'B' is called on storage that 
holds an object of type 'A'}} expected-note{{Destructor of 'B' is called on 
storage that holds an object of type 'A'}}
+}
+
+void restore_after_destroying_placed_object() {
+  B b;
+  b.~B();
+  new (&b) A;
+  reinterpret_cast<A *>(&b)->~A();
+  new (&b) B;
+} // no-warning
+
+void reconstruct_original_type() {
+  B b;
+  b.~B();
+  new (&b) B;
+} // no-warning
+
+void reconstruct_on_heap() {
+  B *b = new B;
+  b->~B();
+  new (b) B;
+  delete b; // no-warning
+}
+
+struct VBase {
+  virtual ~VBase();
+};
+struct VDerived : VBase {};
+
+void delete_through_base_with_virtual_dtor() {
+  VBase *p = new VBase;
+  p->~VBase();
+  new (p) VDerived;
+  delete p; // no-warning
+}
+
+void explicit_virtual_destructor_through_base() {
+  VBase *p = new VBase;
+  p->~VBase();
+  new (p) VDerived;
+  p->~VBase(); // no-warning
+}
+
+void storage_passed_to_opaque_function() {
+  B b;
+  new (&b) A; // expected-note{{Object of type 'A' is created here by 
placement new}}
+  consume(&b);
+} // expected-warning{{Destructor of 'B' is called on storage that holds an 
object of type 'A'}} expected-note{{Destructor of 'B' is called on storage that 
holds an object of type 'A'}}
+
+struct WithDtor {
+  ~WithDtor();
+};
+struct DerivedFromWithDtor : WithDtor {};
+
+void raw_memory_destroyed_through_base() {
+  void *Raw = ::operator new(sizeof(DerivedFromWithDtor));
+  WithDtor *P = new (Raw) DerivedFromWithDtor;
+  P->~WithDtor();
+  ::operator delete(Raw);
+} // no-warning
+
+void raw_local_buffer() {
+  alignas(B) unsigned char Buf[sizeof(B)];
+  B *P = new (Buf) B;
+  P->~B();
+} // no-warning
+
+union OneOf {
+  B First;
+  A Second;
+  OneOf() { new (this) B; }
+  ~OneOf();
+};
+
+void union_member_created_through_union_address() {
+  OneOf U;
+  U.~OneOf();
+  new (&U) OneOf;
+} // no-warning
+
+struct Reassignable {
+  Reassignable();
+  Reassignable(const Reassignable &);
+  ~Reassignable();
+  Reassignable &operator=(const Reassignable &Other) {
+    this->~Reassignable();
+    new (this) Reassignable(Other); // expected-note{{Object of type 
'Reassignable' is created here by placement new}}
+    return *this;
+  }
+};
+struct WrapsReassignable : Reassignable {};
+
+void recreate_base_subobject(const WrapsReassignable &Other) {
+  WrapsReassignable W;
+  W = Other; // expected-note{{Calling implicit copy assignment operator for 
'WrapsReassignable'}} expected-note{{Calling copy assignment operator for 
'Reassignable'}} expected-note{{Returning from copy assignment operator for 
'Reassignable'}} expected-note{{Returning from copy assignment operator for 
'WrapsReassignable'}}
+} // expected-warning{{Destructor of 'WrapsReassignable' is called on storage 
that holds an object of type 'Reassignable'}} expected-note{{Destructor of 
'WrapsReassignable' is called on storage that holds an object of type 
'Reassignable'}}
+
+struct Trivial {
+  int X;
+};
+
+void trivially_destructible_storage() {
+  Trivial t;
+  new (&t) A;
+} // no-warning
+
+struct WithPlacementNew {
+  ~WithPlacementNew();
+  static void *operator new(size_t, void *P) { return P; }
+};
+
+void restore_through_class_placement_new() {
+  WithPlacementNew W;
+  W.~WithPlacementNew();
+  ::new (&W) A;
+  new (&W) WithPlacementNew;
+} // no-warning
+
+struct WithOpaquePlacementNew {
+  ~WithOpaquePlacementNew();
+  static void *operator new(size_t, void *P);
+};
+
+void restore_through_opaque_class_placement_new() {
+  WithOpaquePlacementNew W;
+  W.~WithOpaquePlacementNew();
+  ::new (&W) A;
+  new (&W) WithOpaquePlacementNew;
+} // no-warning
+
+struct OffsetHolder {
+  int X;
+  B Member;
+};
+
+void restore_member_through_byte_offset() {
+  OffsetHolder H;
+  H.Member.~B();
+  ::new (&H.Member) A;
+  ::new (reinterpret_cast<char *>(&H) + __builtin_offsetof(OffsetHolder, 
Member)) B;
+} // no-warning
+
+struct PlacementTag {};
+struct WithTaggedPlacementNew {
+  ~WithTaggedPlacementNew();
+  static void *operator new(size_t, PlacementTag, void *P) { return P; }
+};
+
+void restore_through_tagged_placement_new() {
+  WithTaggedPlacementNew W;
+  W.~WithTaggedPlacementNew();
+  ::new (&W) A;
+  new (PlacementTag{}, &W) WithTaggedPlacementNew;
+} // no-warning
+
+struct PlacedWithClassNew {
+  static void *operator new(size_t, void *P) { return P; }
+};
+
+void replace_through_class_placement_new() {
+  B b;
+  new (&b) PlacedWithClassNew; // expected-note{{Object of type 
'PlacedWithClassNew' is created here by placement new}}
+} // expected-warning{{Destructor of 'B' is called on storage that holds an 
object of type 'PlacedWithClassNew'}} expected-note{{Destructor of 'B' is 
called on storage that holds an object of type 'PlacedWithClassNew'}}
+
+struct EscapingHolder { // expected-warning{{Destructor of 'B' is called on 
storage that holds an object of type 'A'}} expected-note{{Destructor of 'B' is 
called on storage that holds an object of type 'A'}}
+  B Member;
+};
+
+void member_passed_to_opaque_function_with_parent() {
+  EscapingHolder H;
+  new (&H.Member) A; // expected-note{{Object of type 'A' is created here by 
placement new}}
+  consume(&H);
+} // expected-note{{Calling implicit destructor for 'EscapingHolder'}}
+
+struct MutatedHolder { // expected-warning{{Destructor of 'B' is called on 
storage that holds an object of type 'A'}} expected-note{{Destructor of 'B' is 
called on storage that holds an object of type 'A'}}
+  B Member;
+  void mutate();
+};
+
+void member_of_object_with_opaque_method() {
+  MutatedHolder H;
+  new (&H.Member) A; // expected-note{{Object of type 'A' is created here by 
placement new}}
+  H.mutate();
+} // expected-note{{Calling implicit destructor for 'MutatedHolder'}}
+
+struct HeapHolder { // expected-warning{{Destructor of 'B' is called on 
storage that holds an object of type 'A'}} expected-note{{Destructor of 'B' is 
called on storage that holds an object of type 'A'}}
+  B Member;
+};
+
+void heap_member_passed_to_opaque_function_with_parent() {
+  HeapHolder *H = new HeapHolder;
+  new (&H->Member) A; // expected-note{{Object of type 'A' is created here by 
placement new}}
+  consume(H);
+  delete H; // expected-note{{Calling implicit destructor for 'HeapHolder'}}
+}
+
+struct OpaqueDtorHolder {
+  B Member;
+  ~OpaqueDtorHolder();
+};
+
+void member_destroyed_by_opaque_destructor() {
+  OpaqueDtorHolder H;
+  new (&H.Member) A; // expected-note{{Object of type 'A' is created here by 
placement new}}
+} // expected-warning{{Destructor of 'B' is called on storage that holds an 
object of type 'A'}} expected-note{{Destructor of 'B' is called on storage that 
holds an object of type 'A'}}
+
+void member_restored_before_opaque_destructor() {
+  OpaqueDtorHolder H;
+  H.Member.~B();
+  new (&H.Member) A;
+  reinterpret_cast<A *>(&H.Member)->~A();
+  new (&H.Member) B;
+} // no-warning
+
+void array_element() {
+  B Arr[2];
+  new (&Arr[1]) A; // expected-note{{Object of type 'A' is created here by 
placement new}}
+} // expected-warning{{Destructor of 'B' is called on storage that holds an 
object of type 'A'}} expected-note{{Destructor of 'B' is called on storage that 
holds an object of type 'A'}}
+
+void first_array_element() {
+  B Arr[2];
+  new (&Arr[0]) A; // expected-note{{Object of type 'A' is created here by 
placement new}}
+} // expected-warning{{Destructor of 'B' is called on storage that holds an 
object of type 'A'}} expected-note{{Destructor of 'B' is called on storage that 
holds an object of type 'A'}}
+
+void array_element_restored() {
+  B Arr[2];
+  Arr[1].~B();
+  new (&Arr[1]) A;
+  reinterpret_cast<A *>(&Arr[1])->~A();
+  new (&Arr[1]) B;
+} // no-warning
+
+void heap_array_element() {
+  B *Arr = new B[2];
+  new (&Arr[1]) A; // expected-note{{Object of type 'A' is created here by 
placement new}}
+  delete[] Arr; // expected-warning{{Deleting an object of type 'A' through a 
pointer to 'B'}} expected-note{{Deleting an object of type 'A' through a 
pointer to 'B'}}
+}
+
+void heap_first_array_element() {
+  B *Arr = new B[2];
+  new (&Arr[0]) A; // expected-note{{Object of type 'A' is created here by 
placement new}}
+  delete[] Arr; // expected-warning{{Destructor of 'B' is called on storage 
that holds an object of type 'A'}} expected-note{{Destructor of 'B' is called 
on storage that holds an object of type 'A'}}
+}
+
+struct CastBase {
+  ~CastBase();
+};
+struct CastDerived : CastBase {};
+
+void recreate_base_through_reinterpret_cast() {
+  CastDerived D;
+  reinterpret_cast<CastBase *>(&D)->~CastBase();
+  new (reinterpret_cast<CastBase *>(&D)) CastBase; // expected-note{{Object of 
type 'CastBase' is created here by placement new}}
+} // expected-warning{{Destructor of 'CastDerived' is called on storage that 
holds an object of type 'CastBase'}} expected-note{{Destructor of 'CastDerived' 
is called on storage that holds an object of type 'CastBase'}}
+
+void restore_through_array_placement_new() {
+  B b;
+  b.~B();
+  new (&b) A;
+  new (&b) B[1];
+} // no-warning
+
+void heap_destroy_then_replace() {
+  B *P = new B;
+  P->~B();
+  new (P) A; // expected-note{{Object of type 'A' is created here by placement 
new}}
+  delete P; // expected-warning{{Deleting an object of type 'A' through a 
pointer to 'B'}} expected-note{{Deleting an object of type 'A' through a 
pointer to 'B'}}
+}
+
+void heap_recreate_base_through_reinterpret_cast() {
+  CastDerived *P = new CastDerived;
+  P->~CastDerived();
+  new (reinterpret_cast<CastBase *>(P)) CastBase; // expected-note{{Object of 
type 'CastBase' is created here by placement new}}
+  delete P; // expected-warning{{Deleting an object of type 'CastBase' through 
a pointer to 'CastDerived'}} expected-note{{Deleting an object of type 
'CastBase' through a pointer to 'CastDerived'}}
+}
+
+template <class T> void placeInto(void *P) {
+  ::new (P) T; // expected-note{{Object of type 'A' is created here by 
placement new}}
+}
+
+void replace_from_template() {
+  B b;
+  placeInto<A>(&b); // expected-note{{Calling 'placeInto<A>'}} 
expected-note{{Returning from 'placeInto<A>'}}
+} // expected-warning{{Destructor of 'B' is called on storage that holds an 
object of type 'A'}} expected-note{{Destructor of 'B' is called on storage that 
holds an object of type 'A'}}
+
+namespace std {
+template <class T> void destroy_at(T *P) { P->~T(); }
+template <class T> struct default_delete {
+  void operator()(T *P) const { delete P; }
+};
+} // namespace std
+
+void destroy_at_on_replaced_storage() {
+  B b;
+  new (&b) A; // expected-note{{Object of type 'A' is created here by 
placement new}}
+  std::destroy_at(&b); // expected-warning{{Destructor of 'B' is called on 
storage that holds an object of type 'A'}} expected-note{{Destructor of 'B' is 
called on storage that holds an object of type 'A'}}
+  new (&b) B;
+}
+
+void delete_in_std_deleter() {
+  B *P = new B;
+  new (P) A; // expected-note{{Object of type 'A' is created here by placement 
new}}
+  std::default_delete<B>()(P); // expected-warning{{Deleting an object of type 
'A' through a pointer to 'B'}} expected-note{{Deleting an object of type 'A' 
through a pointer to 'B'}}
+}

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

Reply via email to