https://github.com/NeKon69 updated https://github.com/llvm/llvm-project/pull/213439
>From df8c73b63425ab109ec677793bcf0cd80cf2b303 Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Sat, 1 Aug 2026 15:06:50 +0300 Subject: [PATCH 1/2] [LifetimeSafety] Support allocating/freeing functions annotated with ownership_takes/ownership_returns --- .../Analyses/LifetimeSafety/FactsGenerator.h | 10 ++ .../LifetimeSafety/LifetimeAnnotations.h | 10 ++ .../Analysis/Analyses/LifetimeSafety/Loans.h | 16 ++- .../LifetimeSafety/FactsGenerator.cpp | 51 +++++++ .../LifetimeSafety/LifetimeAnnotations.cpp | 14 ++ clang/lib/Analysis/LifetimeSafety/Loans.cpp | 11 +- clang/lib/Sema/SemaLifetimeSafety.h | 20 ++- clang/test/Sema/LifetimeSafety/safety.cpp | 134 ++++++++++++++++++ 8 files changed, 256 insertions(+), 10 deletions(-) diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index 9821078ec1d1e..aabd08d9f68a0 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -117,6 +117,16 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { ArrayRef<const Expr *> Args, bool IsGslConstruction = false); + // Handles calls that allocate memory (e.g. functions annotated with + // `ownership_returns`): the call result is a fresh allocation, so a new + // loan is issued for it. + void handleAllocatingCall(const Expr *Call, const FunctionDecl *FD); + + // Handles calls that free memory (e.g. functions annotated with + // `ownership_takes`): the freed arguments' origins are invalidated. + void handleFreeingCall(const Expr *Call, const FunctionDecl *FD, + ArrayRef<const Expr *> Args); + // Detect methods that invalidate iterators/references/pointees. // For instance methods, Args[0] is the implicit 'this' pointer. void handleInvalidatingCall(const Expr *Call, const FunctionDecl *FD, diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h index 47fcd5dbfd569..0db1bfd74b4d7 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h @@ -102,6 +102,16 @@ bool isInvalidationMethod(const CXXMethodDecl &MD); // (e.g., destructors via implicit 'this', std::destroy_at). bool destructsFirstArg(const FunctionDecl &FD); +/// Returns true if the function allocates memory that must be freed by the +/// caller (e.g. functions annotated with `ownership_returns`, such as +/// `__attribute__((ownership_returns(malloc)))`). +bool isAllocatingFunction(const FunctionDecl &FD); + +/// Returns true if the function frees memory it takes ownership of (e.g. +/// functions annotated with `ownership_takes`, such as +/// `__attribute__((ownership_takes(malloc, 1)))`). +bool isFreeingFunction(const FunctionDecl &FD); + /// Returns true for standard library callable wrappers (e.g., std::function) /// that can propagate the stored lambda's origins. bool isStdCallableWrapperType(const CXXRecordDecl *RD); diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h index 8137a207290d7..9007eddeaf4e6 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h @@ -111,10 +111,15 @@ class PlaceholderBase : public llvm::FoldingSetNode { /// /// TODO: Model access paths of other types, e.g. heap and globals. class AccessPath { - /// The base of the access path: a variable, temporary, or placeholder. + /// The base of the access path: a variable, temporary, placeholder, or an + /// allocation expression (a `CXXNewExpr` or an allocating call). + /// + /// NOTE: Keep the number of alternatives low; PointerUnion packs the + /// discriminator in the low bits of the pointer, so more alternatives + /// require more alignment than 32-bit builds can always provide. const llvm::PointerUnion<const clang::ValueDecl *, const clang::MaterializeTemporaryExpr *, - const PlaceholderBase *, const clang::CXXNewExpr *> + const PlaceholderBase *, const clang::Expr *> Base; /// The path elements representing field accesses and access to unnamed /// interior regions. @@ -125,6 +130,7 @@ class AccessPath { AccessPath(const clang::MaterializeTemporaryExpr *MTE) : Base(MTE) {} AccessPath(const PlaceholderBase *PB) : Base(PB) {} AccessPath(const clang::CXXNewExpr *New) : Base(New) {} + AccessPath(const clang::CallExpr *CE) : Base(CE) {} /// Creates an extended access path by appending a path element. /// Example: AccessPath(x_path, field) creates path to `x.field`. @@ -145,8 +151,10 @@ class AccessPath { return Base.dyn_cast<const PlaceholderBase *>(); } - const clang::CXXNewExpr *getAsNewAllocation() const { - return Base.dyn_cast<const clang::CXXNewExpr *>(); + /// Returns the allocation expression that created this loan: a + /// `CXXNewExpr` or an allocating call. + const clang::Expr *getAsAllocation() const { + return Base.dyn_cast<const clang::Expr *>(); } bool operator==(const AccessPath &RHS) const { diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index ac6267dabf48e..1d8eca0d92eae 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -115,6 +115,15 @@ static const Loan *createLoan(FactManager &FactMgr, const CXXNewExpr *NE) { return FactMgr.getLoanMgr().createLoan(Path, NE); } +/// Creates a loan for a heap allocation made by a call (e.g. a function +/// annotated with `ownership_returns`, like `malloc`). +/// \param CE The CallExpr that represents the allocation +/// \return The new Loan on success, nullptr otherwise +static const Loan *createLoan(FactManager &FactMgr, const CallExpr *CE) { + AccessPath Path(CE); + return FactMgr.getLoanMgr().createLoan(Path, CE); +} + void FactsGenerator::run() { llvm::TimeTraceScope TimeProfile("FactGenerator"); const CFG &Cfg = *AC.getCFG(); @@ -1070,6 +1079,46 @@ void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD, } } +void FactsGenerator::handleFreeingCall(const Expr *Call, const FunctionDecl *FD, + ArrayRef<const Expr *> Args) { + // FIXME: `ParamIdx` accounts for the implicit 'this' parameter of + // constructors, but VisitCXXConstructExpr passes only the explicit + // arguments, so the index mapping would be off by one. + if (isa<CXXConstructorDecl>(FD)) + return; + for (const OwnershipAttr *Attr : FD->specific_attrs<OwnershipAttr>()) { + if (Attr->getOwnKind() != OwnershipAttr::Takes) + continue; + for (const ParamIdx &Idx : Attr->args()) { + // `getLLVMIndex` encodes zero-origin indices including any implicit + // 'this' parameter, matching the layout of the Args array. + unsigned ArgIndex = Idx.getLLVMIndex(); + if (ArgIndex >= Args.size()) + continue; + OriginList *ArgList = getOriginsList(*Args[ArgIndex]); + if (!ArgList) + continue; + CurrentBlockFacts.push_back(FactMgr.createFact<InvalidateOriginFact>( + ArgList->getOuterOriginID(), Call)); + } + } +} + +void FactsGenerator::handleAllocatingCall(const Expr *Call, + const FunctionDecl *FD) { + if (!isAllocatingFunction(*FD)) + return; + const auto *CE = dyn_cast<CallExpr>(Call); + if (!CE) + return; + OriginList *CallList = getOriginsList(*Call); + if (!CallList) + return; + const Loan *L = createLoan(FactMgr, CE); + CurrentBlockFacts.push_back( + FactMgr.createFact<IssueFact>(L->getID(), CallList->getOuterOriginID())); +} + void FactsGenerator::handleFunctionCall(const Expr *Call, const FunctionDecl *FD, ArrayRef<const Expr *> Args, @@ -1087,6 +1136,8 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, handleMovedArgsInCall(FD, Args); handleImplicitObjectFieldUses(Call, FD); handleLifetimeCaptureBy(FD, Args); + handleAllocatingCall(Call, FD); + handleFreeingCall(Call, FD, Args); if (!CallList) return; if (isStdReferenceCast(FD)) { diff --git a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp index 6a52616c5d590..62f178618701b 100644 --- a/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp +++ b/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp @@ -453,6 +453,20 @@ bool destructsFirstArg(const FunctionDecl &FD) { return isInStlNamespace(&FD) && getName(FD) == "destroy_at"; } +bool isAllocatingFunction(const FunctionDecl &FD) { + return llvm::any_of(FD.specific_attrs<OwnershipAttr>(), + [](const OwnershipAttr *Attr) { + return Attr->getOwnKind() == OwnershipAttr::Returns; + }); +} + +bool isFreeingFunction(const FunctionDecl &FD) { + return llvm::any_of(FD.specific_attrs<OwnershipAttr>(), + [](const OwnershipAttr *Attr) { + return Attr->getOwnKind() == OwnershipAttr::Takes; + }); +} + bool isStdCallableWrapperType(const CXXRecordDecl *RD) { if (!RD || !isInStlNamespace(RD)) return false; diff --git a/clang/lib/Analysis/LifetimeSafety/Loans.cpp b/clang/lib/Analysis/LifetimeSafety/Loans.cpp index e71842eefca6a..3974e04c0e7c4 100644 --- a/clang/lib/Analysis/LifetimeSafety/Loans.cpp +++ b/clang/lib/Analysis/LifetimeSafety/Loans.cpp @@ -21,9 +21,14 @@ void AccessPath::dump(llvm::raw_ostream &OS) const { OS << "$" << PVD->getNameAsString(); else if (PB->getImplicitThisParent()) OS << "$this"; - } else if (const auto *E = getAsNewAllocation()) - OS << "NewAllocation at " << E; - else + } else if (const auto *E = getAsAllocation()) { + if (isa<CXXNewExpr>(E)) + OS << "NewAllocation at " << E; + else if (isa<CallExpr>(E)) + OS << "HeapAllocation at " << E; + else + llvm_unreachable("unexpected allocation expression"); + } else llvm_unreachable("access path base invalid"); for (const auto &E : Elements) E.dump(OS); diff --git a/clang/lib/Sema/SemaLifetimeSafety.h b/clang/lib/Sema/SemaLifetimeSafety.h index 1d9f94be7e22d..762e9e336fab9 100644 --- a/clang/lib/Sema/SemaLifetimeSafety.h +++ b/clang/lib/Sema/SemaLifetimeSafety.h @@ -192,7 +192,7 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { reportUseAfterInvalidation(const Expr *IssueExpr, const Expr *UseExpr, const Expr *InvalidationExpr, llvm::ArrayRef<const Expr *> ExprChain) override { - auto WarnDiag = isa<CXXDeleteExpr>(InvalidationExpr) + auto WarnDiag = isFreeingExpr(InvalidationExpr) ? diag::warn_lifetime_safety_use_after_free : diag::warn_lifetime_safety_invalidation; std::string InvalidatedSubject = getDiagSubjectDescription(IssueExpr); @@ -208,7 +208,7 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { const Expr *InvalidationExpr, llvm::ArrayRef<const Expr *> ExprChain) override { - auto WarnDiag = isa<CXXDeleteExpr>(InvalidationExpr) + auto WarnDiag = isFreeingExpr(InvalidationExpr) ? diag::warn_lifetime_safety_use_after_free : diag::warn_lifetime_safety_invalidation; std::string InvalidatedSubject = getDiagSubjectDescription(PVD); @@ -459,6 +459,18 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { } private: + // Returns true if the expression frees memory: a `delete` expression or a + // call to a freeing function (e.g. annotated with `ownership_takes`). + static bool isFreeingExpr(const Expr *InvalidationExpr) { + if (isa<CXXDeleteExpr>(InvalidationExpr)) + return true; + const auto *CE = dyn_cast<CallExpr>(InvalidationExpr); + if (!CE) + return false; + const FunctionDecl *FD = CE->getDirectCallee(); + return FD && isFreeingFunction(*FD); + } + struct LifetimeBoundMacroCache { bool IsBuilt = false; SmallVector<const IdentifierInfo *> Candidates; @@ -531,7 +543,7 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { void reportInvalidationSite(const Expr *InvalidationExpr, StringRef InvalidatedSubject) { - auto Diag = isa<CXXDeleteExpr>(InvalidationExpr) + auto Diag = isFreeingExpr(InvalidationExpr) ? diag::note_lifetime_safety_freed_here : diag::note_lifetime_safety_invalidated_here; S.Diag(InvalidationExpr->getExprLoc(), Diag) @@ -656,6 +668,8 @@ class LifetimeSafetySemaHelperImpl : public LifetimeSafetySemaHelper { llvm::raw_string_ostream OS(Name); FD->getNameForDiagnostic(OS, S.getPrintingPolicy(), /*Qualified=*/false); + if (isAllocatingFunction(*FD)) + return "object allocated by '" + Name + "'"; return "result of call to '" + Name + "'"; } diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp index f3b042cce74b2..f52a3a0710914 100644 --- a/clang/test/Sema/LifetimeSafety/safety.cpp +++ b/clang/test/Sema/LifetimeSafety/safety.cpp @@ -3379,6 +3379,140 @@ void allocate_void_ptr() { } // namespace new_allocation +namespace ownership_functions { + +// Allocating/freeing functions are annotated with the ownership attributes: +// * ownership_returns: the result is a fresh allocation with a new loan. +// * ownership_takes: the annotated arguments are freed, invalidating their +// origins. + +__attribute__((ownership_returns(malloc))) int *myalloc(void); +__attribute__((ownership_takes(malloc, 1))) void myfree(int *p); +__attribute__((ownership_takes(malloc, 1, 2))) void myfree2(int *a, int *b); +__attribute__((ownership_returns(malloc), ownership_takes(malloc, 1))) int * +myrealloc(int *p); + +int *g_owned_ptr; // expected-note {{this global dangles}} + +void ownership_returns_basic_uaf() { + int *p = myalloc(); // expected-warning {{object allocated by 'myalloc' does not live long enough}} + myfree(p); // expected-note {{object allocated by 'myalloc' is freed here}} + *p = 1; // expected-note {{later used here}} +} + +void ownership_returns_free_no_use() { + int *p = myalloc(); + myfree(p); +} + +void ownership_returns_free_stack() { + int local = 42; + int *p = &local; // expected-warning {{local variable 'local' does not live long enough}} + myfree(&local); // expected-note {{local variable 'local' is freed here}} + (void)*p; // expected-note {{later used here}} +} + +void ownership_returns_escape_global() { + int *p = myalloc(); // expected-warning {{object allocated by 'myalloc' escapes to the global variable 'g_owned_ptr' and is later invalidated}} + g_owned_ptr = p; + myfree(p); // expected-note {{object allocated by 'myalloc' is freed here}} +} + +void ownership_returns_global_alias_uaf() { + int *p = myalloc(); // expected-warning {{object allocated by 'myalloc' does not live long enough}} + g_owned_ptr = p; // expected-note {{local variable 'p' aliases the storage of object allocated by 'myalloc'}} + myfree(p); // expected-note {{object allocated by 'myalloc' is freed here}} + *g_owned_ptr = 1; // expected-note {{later used here}} + g_owned_ptr = nullptr; +} + +struct OwnershipFieldHolder { + int *f; // expected-note {{this field dangles}} + void go() { + int *p = myalloc(); // expected-warning {{object allocated by 'myalloc' escapes to the field 'f' and is later invalidated}} + this->f = p; + myfree(p); // expected-note {{object allocated by 'myalloc' is freed here}} + } +}; + +void ownership_returns_heap_escape_ok() { + int *p = myalloc(); + g_owned_ptr = p; + myfree(p); + g_owned_ptr = nullptr; +} + +void ownership_returns_free_param(int *p) { // expected-warning {{parameter 'p' does not live long enough}} + myfree(p); // expected-note {{parameter 'p' is freed here}} + *p = 1; // expected-note {{later used here}} +} + +void ownership_returns_multi_arg_free() { + int *a = myalloc(); // expected-warning {{object allocated by 'myalloc' does not live long enough}} + int *b = myalloc(); // expected-warning {{object allocated by 'myalloc' does not live long enough}} + myfree2(a, b); // expected-note 2 {{object allocated by 'myalloc' is freed here}} + *a = 1; // expected-note {{later used here}} + *b = 1; // expected-note {{later used here}} +} + +void ownership_returns_realloc() { + int *p = myalloc(); + p = myrealloc(p); // expected-warning {{object allocated by 'myrealloc' does not live long enough}} + *p = 1; + myfree(p); // expected-note {{object allocated by 'myrealloc' is freed here}} + *p = 1; // expected-note {{later used here}} +} + +void ownership_returns_free_then_reassign() { + int *p = myalloc(); + myfree(p); + p = myalloc(); + *p = 1; + myfree(p); +} + +void ownership_returns_conditional_free(int k) { + int *p = myalloc(); // expected-warning {{object allocated by 'myalloc' does not live long enough}} + if (k) + myfree(p); // expected-note {{object allocated by 'myalloc' is freed here}} + else + myfree(p); + *p = 1; // expected-note {{later used here}} +} + +void ownership_returns_double_free() { + int *p = myalloc(); // expected-warning {{object allocated by 'myalloc' does not live long enough}} + myfree(p); // expected-note {{object allocated by 'myalloc' is freed here}} + myfree(p); // expected-note {{later used here}} +} + +struct OwnershipAllocator { + __attribute__((ownership_returns(malloc))) int *alloc(void); + // The index accounts for the implicit 'this' parameter, so 2 is the first + // explicit parameter. + __attribute__((ownership_takes(malloc, 2))) void dealloc(int *p); + __attribute__((ownership_returns(malloc), ownership_takes(malloc, 2))) int * + realloc2(int *p); +}; + +void ownership_returns_member_allocator() { + OwnershipAllocator a; + int *p = a.alloc(); // expected-warning {{object allocated by 'alloc' does not live long enough}} + a.dealloc(p); // expected-note {{object allocated by 'alloc' is freed here}} + *p = 1; // expected-note {{later used here}} +} + +void ownership_returns_member_realloc() { + OwnershipAllocator a; + int *p = a.alloc(); + p = a.realloc2(p); // expected-warning {{object allocated by 'realloc2' does not live long enough}} + *p = 1; + a.dealloc(p); // expected-note {{object allocated by 'realloc2' is freed here}} + *p = 1; // expected-note {{later used here}} +} + +} // namespace ownership_functions + namespace placement_new { void placement_new_int_basic() { >From 682a88eff01e7e8e2b23de7ca6e653bc412a7a1c Mon Sep 17 00:00:00 2001 From: NeKon69 <[email protected]> Date: Mon, 3 Aug 2026 17:47:36 +0300 Subject: [PATCH 2/2] address review --- .../LifetimeSafety/FactsGenerator.cpp | 5 +- clang/test/Sema/LifetimeSafety/safety.cpp | 58 +++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index 1d8eca0d92eae..61c3b08bc1a59 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -119,7 +119,8 @@ static const Loan *createLoan(FactManager &FactMgr, const CXXNewExpr *NE) { /// annotated with `ownership_returns`, like `malloc`). /// \param CE The CallExpr that represents the allocation /// \return The new Loan on success, nullptr otherwise -static const Loan *createLoan(FactManager &FactMgr, const CallExpr *CE) { +static const Loan *createNewAllocLoan(FactManager &FactMgr, + const CallExpr *CE) { AccessPath Path(CE); return FactMgr.getLoanMgr().createLoan(Path, CE); } @@ -1114,7 +1115,7 @@ void FactsGenerator::handleAllocatingCall(const Expr *Call, OriginList *CallList = getOriginsList(*Call); if (!CallList) return; - const Loan *L = createLoan(FactMgr, CE); + const Loan *L = createNewAllocLoan(FactMgr, CE); CurrentBlockFacts.push_back( FactMgr.createFact<IssueFact>(L->getID(), CallList->getOuterOriginID())); } diff --git a/clang/test/Sema/LifetimeSafety/safety.cpp b/clang/test/Sema/LifetimeSafety/safety.cpp index f52a3a0710914..a89dde9fee676 100644 --- a/clang/test/Sema/LifetimeSafety/safety.cpp +++ b/clang/test/Sema/LifetimeSafety/safety.cpp @@ -3511,6 +3511,64 @@ void ownership_returns_member_realloc() { *p = 1; // expected-note {{later used here}} } +struct UserDefinedNewDeleteOps { + int x; + static void *operator new(std::size_t) + __attribute__((ownership_returns(malloc))); + static void operator delete(void *) + __attribute__((ownership_takes(malloc, 1))); + // Inverse case: a placement `new` (returns memory) annotated to take/free + // its placement argument. + static void *operator new(std::size_t, void *p) + __attribute__((ownership_takes(malloc, 2))); +}; + +void user_defined_new_delete_use_after_free() { + UserDefinedNewDeleteOps *p = new UserDefinedNewDeleteOps; // expected-warning {{allocated object does not live long enough}} + delete p; // expected-note {{allocated object is freed here}} + p->x = 1; // expected-note {{later used here}} +} + +void user_defined_new_delete_explicit_call_use_after_free() { + UserDefinedNewDeleteOps *p = new UserDefinedNewDeleteOps; // expected-warning {{allocated object does not live long enough}} + UserDefinedNewDeleteOps::operator delete(p); // expected-note {{allocated object is freed here}} + p->x = 1; // expected-note {{later used here}} +} + +void user_defined_new_delete_explicit_allocator_use_after_free() { + auto *p = static_cast<UserDefinedNewDeleteOps *>( + UserDefinedNewDeleteOps::operator new(sizeof(UserDefinedNewDeleteOps))); // expected-warning {{expression does not live long enough}} + UserDefinedNewDeleteOps::operator delete(p); // expected-note {{expression is freed here}} + p->x = 1; // expected-note {{later used here}} +} + +// FIXME: The ownership attribute is only honored here because the operator is +// called explicitly (a CallExpr). In +// `user_defined_new_delete_placement_expr_inert` the same annotated operator +// is invoked by a `new` expression, which skips the attribute handling. +void user_defined_new_delete_placement_explicit_call_use_after_free() { + int local = 42; + int *p = &local; // expected-warning {{local variable 'local' does not live long enough}} + UserDefinedNewDeleteOps::operator new(sizeof(UserDefinedNewDeleteOps), p); // expected-note {{local variable 'local' is freed here}} + *p = 1; // expected-note {{later used here}} +} + +void user_defined_new_delete_placement_expr_inert() { + char buf[sizeof(UserDefinedNewDeleteOps)]; + // The class's annotated placement `operator new` is used here, but the + // `new` expression path does not consult the ownership attributes. + auto *p = new (buf) UserDefinedNewDeleteOps; + p->x = 1; +} + +void user_defined_new_delete_free_then_reassign() { + auto *p = static_cast<UserDefinedNewDeleteOps *>( + UserDefinedNewDeleteOps::operator new(sizeof(UserDefinedNewDeleteOps))); + UserDefinedNewDeleteOps::operator delete(p); + p = new UserDefinedNewDeleteOps; + delete p; +} + } // namespace ownership_functions namespace placement_new { _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
