diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index f6b936f5ccd9..496d86ee2fe7 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -349,6 +349,8 @@ def note_constexpr_new_delete_mismatch : Note< "used to delete pointer to " "%select{array object of type %2|non-array object of type %2|" "object allocated with 'new'}0}1">; +def note_constexpr_deallocate_null : Note< + "'std::allocator<...>::deallocate' used to delete a null pointer">; def note_constexpr_delete_subobject : Note< "delete of pointer%select{ to subobject|}1 '%0' " "%select{|that does not point to complete object}1">; diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index ae7131eae01d..4213beb915af 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -6712,9 +6712,12 @@ bool HandleOperatorDeleteCall(EvalInfo &Info, const CallExpr *E) { if (Pointer.Designator.Invalid) return false; - // Deleting a null pointer has no effect. - if (Pointer.isNullPointer()) + // Deleting a null pointer would have no effect, but it's not permitted by + // std::allocator::deallocate's contract. + if (Pointer.isNullPointer()) { + Info.CCEDiag(E->getExprLoc(), diag::note_constexpr_deallocate_null); return true; + } if (!CheckDeleteKind(Info, E, Pointer, DynAlloc::StdAllocator)) return false; diff --git a/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp b/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp index 097ca00640e9..f37ebc9f6365 100644 --- a/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp +++ b/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp @@ -17,7 +17,7 @@ namespace std { return (T*)NEW(sizeof(T) * N); // expected-note 3{{heap allocation}} expected-note {{not deallocated}} } constexpr void deallocate(void *p) { - DELETE(p); // expected-note 2{{'std::allocator<...>::deallocate' used to delete pointer to object allocated with 'new'}} + DELETE(p); // #dealloc expected-note 2{{'std::allocator<...>::deallocate' used to delete pointer to object allocated with 'new'}} } }; } @@ -83,6 +83,11 @@ static_assert(mismatched(2, 2)); constexpr int *escape = std::allocator().allocate(3); // expected-error {{constant expression}} expected-note {{pointer to subobject of heap-allocated}} constexpr int leak = (std::allocator().allocate(3), 0); // expected-error {{constant expression}} constexpr int no_lifetime_start = (*std::allocator().allocate(1) = 1); // expected-error {{constant expression}} expected-note {{assignment to object outside its lifetime}} +constexpr int no_deallocate_nullptr = (std::allocator().deallocate(nullptr), 1); // expected-error {{constant expression}} expected-note {{in call}} +// expected-note@#dealloc {{'std::allocator<...>::deallocate' used to delete a null pointer}} +constexpr int no_deallocate_nonalloc = (std::allocator().deallocate((int*)&no_deallocate_nonalloc), 1); // expected-error {{constant expression}} expected-note {{in call}} +// expected-note@#dealloc {{delete of pointer '&no_deallocate_nonalloc' that does not point to a heap-allocated object}} +// expected-note@-2 {{declared here}} void *operator new(std::size_t, void *p) { return p; } constexpr bool no_placement_new_in_user_code() { // expected-error {{never produces a constant expression}}