PR49465: Disallow constant evaluation of a call to operator delete(nullptr).

The only time we would consider allowing this is inside a call to
std::allocator<T>::deallocate, whose contract does not permit deletion
of null pointers.
This commit is contained in:
Richard Smith 2021-03-09 15:04:51 -08:00
parent 234f3211a3
commit a892b0015e
3 changed files with 13 additions and 3 deletions

View File

@ -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">;

View File

@ -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<T>::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;

View File

@ -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<int>().allocate(3); // expected-error {{constant expression}} expected-note {{pointer to subobject of heap-allocated}}
constexpr int leak = (std::allocator<int>().allocate(3), 0); // expected-error {{constant expression}}
constexpr int no_lifetime_start = (*std::allocator<int>().allocate(1) = 1); // expected-error {{constant expression}} expected-note {{assignment to object outside its lifetime}}
constexpr int no_deallocate_nullptr = (std::allocator<int>().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<int>().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}}