https://github.com/hvdijk created https://github.com/llvm/llvm-project/pull/149406
P3144R2 made it ill-formed to delete a pointer to an incomplete class type, and asserted that incomplete class types were the only incomplete types that were possible to pass to the delete operator. This assertion was wrong, and PR 99278 already updated the check to exclude incomplete enum types, but that is still wrong: pointers to arrays of unknown length are another instance of incomplete types that are possible to delete and were not included in PR3144R2's ban. Additionally, the diagnostic still claimed that the problem was with deleting pointers to incomplete types, rather than to incomplete class types. This PR ensures that when we implement the check for incomplete class type, we only check for incomplete class type, and adjusts the diagnostics to say 'incomplete struct' or 'incomplete union' to be accurate without being overly verbose. >From 94e652bc0bd6a04ef61a9f9b703257d464c7b714 Mon Sep 17 00:00:00 2001 From: Harald van Dijk <harald.vand...@codeplay.com> Date: Thu, 17 Jul 2025 21:47:30 +0100 Subject: [PATCH] More fixes for P3144R2 implementation P3144R2 made it ill-formed to delete a pointer to an incomplete class type, and asserted that incomplete class types were the only incomplete types that were possible to pass to the delete operator. This assertion was wrong, and PR 99278 already updated the check to exclude incomplete enum types, but that is still wrong: pointers to arrays of unknown length are another instance of incomplete types that are possible to delete and were not included in PR3144R2's ban. Additionally, the diagnostic still claimed that the problem was with deleting pointers to incomplete types, rather than to incomplete class types. This PR ensures that when we implement the check for incomplete class type, we only check for incomplete class type, and adjusts the diagnostics to say 'incomplete struct' or 'incomplete union' to be accurate without being overly verbose. --- .../clang/Basic/DiagnosticSemaKinds.td | 6 ++++-- clang/lib/Sema/SemaExprCXX.cpp | 21 +++++++++---------- clang/test/Analysis/dtor.cpp | 2 +- clang/test/CXX/drs/cwg5xx.cpp | 6 +++--- .../CXX/expr/expr.unary/expr.delete/p5.cpp | 9 ++++++-- clang/test/OpenMP/deferred-diags.cpp | 2 +- clang/test/SemaCXX/new-delete.cpp | 14 ++++++++----- 7 files changed, 35 insertions(+), 25 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index b2ea65ae111be..d86f10da17c61 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -8392,17 +8392,19 @@ def ext_default_init_const : ExtWarn< "is a Microsoft extension">, InGroup<MicrosoftConstInit>; def err_delete_operand : Error<"cannot delete expression of type %0">; +def err_delete_void_ptr_operand : Error< + "cannot delete expression with pointer-to-'void' type %0">; def ext_delete_void_ptr_operand : ExtWarn< "cannot delete expression with pointer-to-'void' type %0">, InGroup<DeleteIncomplete>; def err_ambiguous_delete_operand : Error< "ambiguous conversion of delete expression of type %0 to a pointer">; def warn_delete_incomplete : Warning< - "deleting pointer to incomplete type %0 is incompatible with C++2c" + "deleting pointer to incomplete %select{struct|union}0 %1 is incompatible with C++2c" " and may cause undefined behavior">, InGroup<DeleteIncomplete>; def err_delete_incomplete : Error< - "cannot delete pointer to incomplete type %0">; + "cannot delete pointer to incomplete %select{struct|union}0 %1">; def err_delete_incomplete_class_type : Error< "deleting incomplete class type %0; no conversions to pointer type">; def err_delete_explicit_conversion : Error< diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index f851c9e1d5015..a741acc8a0a89 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -4033,25 +4033,24 @@ Sema::ActOnCXXDelete(SourceLocation StartLoc, bool UseGlobal, // effectively bans deletion of "void*". However, most compilers support // this, so we treat it as a warning unless we're in a SFINAE context. // But we still prohibit this since C++26. - Diag(StartLoc, LangOpts.CPlusPlus26 ? diag::err_delete_incomplete + Diag(StartLoc, LangOpts.CPlusPlus26 ? diag::err_delete_void_ptr_operand : diag::ext_delete_void_ptr_operand) - << (LangOpts.CPlusPlus26 ? Pointee : Type) - << Ex.get()->getSourceRange(); + << Type << Ex.get()->getSourceRange(); } else if (Pointee->isFunctionType() || Pointee->isVoidType() || Pointee->isSizelessType()) { return ExprError(Diag(StartLoc, diag::err_delete_operand) - << Type << Ex.get()->getSourceRange()); + << Type << Ex.get()->getSourceRange()); } else if (!Pointee->isDependentType()) { // FIXME: This can result in errors if the definition was imported from a // module but is hidden. - if (Pointee->isEnumeralType() || - !RequireCompleteType(StartLoc, Pointee, - LangOpts.CPlusPlus26 - ? diag::err_delete_incomplete - : diag::warn_delete_incomplete, - Ex.get())) { - if (const RecordType *RT = PointeeElem->getAs<RecordType>()) + if (const RecordType *RT = PointeeElem->getAs<RecordType>()) { + if (!RequireCompleteType(StartLoc, Pointee, + LangOpts.CPlusPlus26 + ? diag::err_delete_incomplete + : diag::warn_delete_incomplete, + RT->isUnionType(), Ex.get())) { PointeeRD = cast<CXXRecordDecl>(RT->getDecl()); + } } } diff --git a/clang/test/Analysis/dtor.cpp b/clang/test/Analysis/dtor.cpp index c17c886d97fb4..66f555cd65240 100644 --- a/clang/test/Analysis/dtor.cpp +++ b/clang/test/Analysis/dtor.cpp @@ -499,7 +499,7 @@ namespace PseudoDtor { namespace Incomplete { class Foo; // expected-note{{forward declaration}} - void f(Foo *foo) { delete foo; } // expected-warning{{deleting pointer to incomplete type}} + void f(Foo *foo) { delete foo; } // expected-warning{{deleting pointer to incomplete struct 'Foo'}} } namespace TypeTraitExpr { diff --git a/clang/test/CXX/drs/cwg5xx.cpp b/clang/test/CXX/drs/cwg5xx.cpp index 1d505adecfb27..269397a0dc221 100644 --- a/clang/test/CXX/drs/cwg5xx.cpp +++ b/clang/test/CXX/drs/cwg5xx.cpp @@ -907,7 +907,7 @@ namespace cwg573 { // cwg573: no // cxx98-error@-1 {{cast between pointer-to-function and pointer-to-object is an extension}} void f() { delete a; } // cxx98-23-error@-1 {{cannot delete expression with pointer-to-'void' type 'void *'}} - // since-cxx26-error@-2 {{cannot delete pointer to incomplete type 'void'}} + // since-cxx26-error@-2 {{cannot delete expression with pointer-to-'void' type 'void *'}} int n = d - a; // expected-error@-1 {{arithmetic on pointers to void}} // FIXME: This is ill-formed. @@ -1298,12 +1298,12 @@ namespace cwg599 { // cwg599: partial void f(void *p, void (*q)(), S s, T t, U u, V v) { delete p; // cxx98-23-error@-1 {{cannot delete expression with pointer-to-'void' type 'void *'}} - // since-cxx26-error@-2 {{cannot delete pointer to incomplete type 'void'}} + // since-cxx26-error@-2 {{cannot delete expression with pointer-to-'void' type 'void *'}} delete q; // expected-error@-1 {{cannot delete expression of type 'void (*)()'}} delete s; // cxx98-23-error@-1 {{cannot delete expression with pointer-to-'void' type 'void *'}} - // since-cxx26-error@-2 {{cannot delete pointer to incomplete type 'void'}} + // since-cxx26-error@-2 {{cannot delete expression with pointer-to-'void' type 'void *'}} delete t; // expected-error@-1 {{cannot delete expression of type 'T'}} // FIXME: This is valid, but is rejected due to a non-conforming GNU diff --git a/clang/test/CXX/expr/expr.unary/expr.delete/p5.cpp b/clang/test/CXX/expr/expr.unary/expr.delete/p5.cpp index ecb29189af60b..4e68fbab5d670 100644 --- a/clang/test/CXX/expr/expr.unary/expr.delete/p5.cpp +++ b/clang/test/CXX/expr/expr.unary/expr.delete/p5.cpp @@ -6,12 +6,12 @@ // The trivial case. class T0; // expected-note {{forward declaration}} -void f0(T0 *a) { delete a; } // expected-warning {{deleting pointer to incomplete type}} +void f0(T0 *a) { delete a; } // expected-warning {{deleting pointer to incomplete struct 'T0'}} class T0 { ~T0(); }; // The trivial case, inside a template instantiation. template<typename T> -struct T1_A { T *x; ~T1_A() { delete x; } }; // expected-warning {{deleting pointer to incomplete type}} +struct T1_A { T *x; ~T1_A() { delete x; } }; // expected-warning {{deleting pointer to incomplete struct 'T1_B'}} class T1_B; // expected-note {{forward declaration}} void f0() { T1_A<T1_B> x; } // expected-note {{in instantiation of member function}} @@ -44,3 +44,8 @@ class T3_A { private: ~T3_A(); // expected-note{{declared private here}} }; + +// The trivial case but with a union. +union T4; // expected-note {{forward declaration}} +void f4(T4 *a) { delete a; } // expected-warning {{deleting pointer to incomplete union 'T4'}} +union T4 { ~T4(); }; diff --git a/clang/test/OpenMP/deferred-diags.cpp b/clang/test/OpenMP/deferred-diags.cpp index e31b99b8c88e4..a978465322e9d 100644 --- a/clang/test/OpenMP/deferred-diags.cpp +++ b/clang/test/OpenMP/deferred-diags.cpp @@ -41,7 +41,7 @@ namespace TestDeleteIncompleteClassDefinition { struct a; struct b { b() { - delete c; // expected-warning {{deleting pointer to incomplete type 'a' is incompatible with C++2c and may cause undefined behavior}} + delete c; // expected-warning {{deleting pointer to incomplete struct 'a' is incompatible with C++2c and may cause undefined behavior}} } a *c; }; diff --git a/clang/test/SemaCXX/new-delete.cpp b/clang/test/SemaCXX/new-delete.cpp index f918501554f80..735908c5dc0bb 100644 --- a/clang/test/SemaCXX/new-delete.cpp +++ b/clang/test/SemaCXX/new-delete.cpp @@ -225,10 +225,10 @@ void bad_deletes() delete [0] (int*)0; // expected-error {{expected variable name or 'this' in lambda capture list}} delete (void*)0; // cxx98-23-warning@-1 {{cannot delete expression with pointer-to-'void' type 'void *'}} - // since-cxx26-error@-2 {{cannot delete pointer to incomplete type 'void'}} + // since-cxx26-error@-2 {{cannot delete expression with pointer-to-'void' type 'void *'}} delete (T*)0; - // cxx98-23-warning@-1 {{deleting pointer to incomplete type}} - // since-cxx26-error@-2 {{cannot delete pointer to incomplete type 'T'}} + // cxx98-23-warning@-1 {{deleting pointer to incomplete struct 'T'}} + // since-cxx26-error@-2 {{cannot delete pointer to incomplete struct 'T'}} ::S::delete (int*)0; // expected-error {{expected unqualified-id}} } @@ -570,8 +570,8 @@ namespace DeleteIncompleteClassPointerError { struct A; // expected-note {{forward declaration}} void f(A *x) { 1+delete x; } // expected-error@-1 {{invalid operands to binary expression}} - // cxx98-23-warning@-2 {{deleting pointer to incomplete type}} - // since-cxx26-error@-3 {{cannot delete pointer to incomplete type 'A'}} + // cxx98-23-warning@-2 {{deleting pointer to incomplete struct 'A'}} + // since-cxx26-error@-3 {{cannot delete pointer to incomplete struct 'A'}} } namespace PR10504 { @@ -595,6 +595,10 @@ struct GH99278_2 { } f; }; GH99278_2<void> e; +void GH99278_3(int(*p)[]) { + delete p; + // expected-warning@-1 {{'delete' applied to a pointer-to-array type 'int (*)[]' treated as 'delete[]'}} +}; #endif struct PlacementArg {}; _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits