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
