Author: Mital Ashok Date: 2024-08-20T18:31:52+02:00 New Revision: 0e24686af49d2f9e50438d3a27db6f1ade594855
URL: https://github.com/llvm/llvm-project/commit/0e24686af49d2f9e50438d3a27db6f1ade594855 DIFF: https://github.com/llvm/llvm-project/commit/0e24686af49d2f9e50438d3a27db6f1ade594855.diff LOG: [Clang] CWG722: nullptr to ellipses (#104704) https://cplusplus.github.io/CWG/issues/722.html nullptr passed to a variadic function now converted to void* in C++. This does not affect C23 nullptr. Also fixes -Wformat-pedantic so that it no longer warns for nullptr passed to %p (because it is converted to void* in C++ and it is allowed for va_arg(ap, void*) in C23) Added: clang/test/CXX/drs/cwg722.cpp clang/test/Sema/format-pointer.c Modified: clang/docs/ReleaseNotes.rst clang/lib/AST/FormatString.cpp clang/lib/Sema/SemaExpr.cpp clang/test/CXX/drs/cwg7xx.cpp clang/test/Sema/format-strings-pedantic.c clang/www/cxx_dr_status.html Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 0167e7f1e8e3de..8435ca3c4f5050 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -149,6 +149,9 @@ Resolutions to C++ Defect Reports of the target type, even if the type of the bit-field is larger. (`CWG2627: Bit-fields and narrowing conversions <https://cplusplus.github.io/CWG/issues/2627.html>`_) +- ``nullptr`` is now promoted to ``void*`` when passed to a C-style variadic function. + (`CWG722: Can nullptr be passed to an ellipsis? <https://cplusplus.github.io/CWG/issues/722.html>`_) + C Language Changes ------------------ diff --git a/clang/lib/AST/FormatString.cpp b/clang/lib/AST/FormatString.cpp index da8164bad518ec..e892c1592df986 100644 --- a/clang/lib/AST/FormatString.cpp +++ b/clang/lib/AST/FormatString.cpp @@ -520,33 +520,18 @@ ArgType::matchesType(ASTContext &C, QualType argTy) const { return NoMatch; } - case CStrTy: { - const PointerType *PT = argTy->getAs<PointerType>(); - if (!PT) - return NoMatch; - QualType pointeeTy = PT->getPointeeType(); - if (const BuiltinType *BT = pointeeTy->getAs<BuiltinType>()) - switch (BT->getKind()) { - case BuiltinType::Char_U: - case BuiltinType::UChar: - case BuiltinType::Char_S: - case BuiltinType::SChar: - return Match; - default: - break; - } - + case CStrTy: + if (const auto *PT = argTy->getAs<PointerType>(); + PT && PT->getPointeeType()->isCharType()) + return Match; return NoMatch; - } - case WCStrTy: { - const PointerType *PT = argTy->getAs<PointerType>(); - if (!PT) - return NoMatch; - QualType pointeeTy = - C.getCanonicalType(PT->getPointeeType()).getUnqualifiedType(); - return pointeeTy == C.getWideCharType() ? Match : NoMatch; - } + case WCStrTy: + if (const auto *PT = argTy->getAs<PointerType>(); + PT && + C.hasSameUnqualifiedType(PT->getPointeeType(), C.getWideCharType())) + return Match; + return NoMatch; case WIntTy: { QualType WInt = C.getCanonicalType(C.getWIntType()).getUnqualifiedType(); @@ -569,15 +554,25 @@ ArgType::matchesType(ASTContext &C, QualType argTy) const { } case CPointerTy: - if (argTy->isVoidPointerType()) { - return Match; - } if (argTy->isPointerType() || argTy->isObjCObjectPointerType() || - argTy->isBlockPointerType() || argTy->isNullPtrType()) { + if (const auto *PT = argTy->getAs<PointerType>()) { + QualType PointeeTy = PT->getPointeeType(); + if (PointeeTy->isVoidType() || (!Ptr && PointeeTy->isCharType())) + return Match; return NoMatchPedantic; - } else { - return NoMatch; } + // nullptr_t* is not a double pointer, so reject when something like + // void** is expected. + // In C++, nullptr is promoted to void*. In C23, va_arg(ap, void*) is not + // undefined when the next argument is of type nullptr_t. + if (!Ptr && argTy->isNullPtrType()) + return C.getLangOpts().CPlusPlus ? MatchPromotion : Match; + + if (argTy->isObjCObjectPointerType() || argTy->isBlockPointerType()) + return NoMatchPedantic; + + return NoMatch; + case ObjCPointerTy: { if (argTy->getAs<ObjCObjectPointerType>() || argTy->getAs<BlockPointerType>()) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index f495658fe58641..c67183df335dd5 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -923,6 +923,13 @@ ExprResult Sema::DefaultArgumentPromotion(Expr *E) { E = Temp.get(); } + // C++ [expr.call]p7, per CWG722: + // An argument that has (possibly cv-qualified) type std::nullptr_t is + // converted to void* ([conv.ptr]). + // (This does not apply to C23 nullptr) + if (getLangOpts().CPlusPlus && E->getType()->isNullPtrType()) + E = ImpCastExprToType(E, Context.VoidPtrTy, CK_NullToPointer).get(); + return E; } @@ -933,9 +940,9 @@ Sema::VarArgKind Sema::isValidVarArgType(const QualType &Ty) { // enumeration, pointer, pointer to member, or class type, the program // is ill-formed. // - // Since we've already performed array-to-pointer and function-to-pointer - // decay, the only such type in C++ is cv void. This also handles - // initializer lists as variadic arguments. + // Since we've already performed null pointer conversion, array-to-pointer + // decay and function-to-pointer decay, the only such type in C++ is cv + // void. This also handles initializer lists as variadic arguments. if (Ty->isVoidType()) return VAK_Invalid; diff --git a/clang/test/CXX/drs/cwg722.cpp b/clang/test/CXX/drs/cwg722.cpp new file mode 100644 index 00000000000000..e90d9af360d9d0 --- /dev/null +++ b/clang/test/CXX/drs/cwg722.cpp @@ -0,0 +1,56 @@ +// RUN: %clang_cc1 -std=c++98 %s -verify -pedantic-errors +// RUN: %clang_cc1 -std=c++11 %s -verify -pedantic-errors -ast-dump | FileCheck %s +// RUN: %clang_cc1 -std=c++14 %s -verify -pedantic-errors -ast-dump | FileCheck %s +// RUN: %clang_cc1 -std=c++17 %s -verify -pedantic-errors -ast-dump | FileCheck %s +// RUN: %clang_cc1 -std=c++20 %s -verify -pedantic-errors -ast-dump | FileCheck %s +// RUN: %clang_cc1 -std=c++23 %s -verify -pedantic-errors -ast-dump | FileCheck %s +// RUN: %clang_cc1 -std=c++26 %s -verify -pedantic-errors -ast-dump | FileCheck %s + +// expected-no-diagnostics +// cwg722: 20 + +#if __cplusplus >= 201103L +namespace std { + using nullptr_t = decltype(nullptr); +} + +void f(std::nullptr_t...); +std::nullptr_t g(); +void h() { + std::nullptr_t np; + const std::nullptr_t cnp = nullptr; + extern int i; + f( + nullptr, + nullptr, np, cnp, + static_cast<std::nullptr_t>(np), + g(), + __builtin_bit_cast(std::nullptr_t, static_cast<void*>(&i)) + ); +// CHECK: `-CallExpr {{.+}} 'void' +// CHECK-NEXT: |-ImplicitCastExpr {{.+}} 'void (*)(std::nullptr_t, ...)' <FunctionToPointerDecay> +// CHECK-NEXT: | `-DeclRefExpr {{.+}} 'void (std::nullptr_t, ...)' lvalue Function {{.+}} 'f' 'void (std::nullptr_t, ...)' +// CHECK-NEXT: |-CXXNullPtrLiteralExpr {{.+}} 'std::nullptr_t' +// CHECK-NEXT: |-ImplicitCastExpr {{.+}} 'void *' <NullToPointer> +// CHECK-NEXT: | `-CXXNullPtrLiteralExpr {{.+}} 'std::nullptr_t' +// CHECK-NEXT: |-ImplicitCastExpr {{.+}} 'void *' <NullToPointer> +// CHECK-NEXT: | `-DeclRefExpr {{.+}} 'std::nullptr_t' lvalue Var {{.+}} 'np' 'std::nullptr_t' +// CHECK-NEXT: |-ImplicitCastExpr {{.+}} 'void *' <NullToPointer> +// CHECK-NEXT: | `-DeclRefExpr {{.+}} 'const std::nullptr_t' lvalue Var {{.+}} 'cnp' 'const std::nullptr_t' +// CHECK-NEXT: |-ImplicitCastExpr {{.+}} 'void *' <NullToPointer> +// CHECK-NEXT: | `-CXXStaticCastExpr {{.+}} 'std::nullptr_t' static_cast<std::nullptr_t> <NoOp> +// CHECK-NEXT: | `-ImplicitCastExpr {{.+}} 'std::nullptr_t' <NullToPointer> part_of_explicit_cast +// CHECK-NEXT: | `-DeclRefExpr {{.+}} 'std::nullptr_t' lvalue Var {{.+}} 'np' 'std::nullptr_t' +// CHECK-NEXT: |-ImplicitCastExpr {{.+}} 'void *' <NullToPointer> +// CHECK-NEXT: | `-CallExpr {{.+}} 'std::nullptr_t' +// CHECK-NEXT: | `-ImplicitCastExpr {{.+}} 'std::nullptr_t (*)()' <FunctionToPointerDecay> +// CHECK-NEXT: | `-DeclRefExpr {{.+}} 'std::nullptr_t ()' lvalue Function {{.+}} 'g' 'std::nullptr_t ()' +// CHECK-NEXT: `-ImplicitCastExpr {{.+}} 'void *' <NullToPointer> +// CHECK-NEXT: `-BuiltinBitCastExpr {{.+}} 'std::nullptr_t' <LValueToRValueBitCast> +// CHECK-NEXT: `-MaterializeTemporaryExpr {{.+}} 'void *' xvalue +// CHECK-NEXT: `-CXXStaticCastExpr {{.+}} 'void *' static_cast<void *> <NoOp> +// CHECK-NEXT: `-ImplicitCastExpr {{.+}} 'void *' <BitCast> part_of_explicit_cast +// CHECK-NEXT: `-UnaryOperator {{.+}} 'int *' prefix '&' cannot overflow +// CHECK-NEXT: `-DeclRefExpr {{.+}} 'int' lvalue Var {{.+}} 'i' 'int' +} +#endif diff --git a/clang/test/CXX/drs/cwg7xx.cpp b/clang/test/CXX/drs/cwg7xx.cpp index d120eb2487aff3..507eb8fb714350 100644 --- a/clang/test/CXX/drs/cwg7xx.cpp +++ b/clang/test/CXX/drs/cwg7xx.cpp @@ -96,6 +96,8 @@ static_assert(!is_volatile<void()volatile&>::value, ""); #endif } // namespace cwg713 +// cwg722 is in cwg722.cpp + namespace cwg727 { // cwg727: partial struct A { template<typename T> struct C; // #cwg727-C diff --git a/clang/test/Sema/format-pointer.c b/clang/test/Sema/format-pointer.c new file mode 100644 index 00000000000000..2a94df01124eec --- /dev/null +++ b/clang/test/Sema/format-pointer.c @@ -0,0 +1,53 @@ +// RUN: %clang_cc1 -Wformat %s -verify +// RUN: %clang_cc1 -Wformat -std=c23 %s -verify +// RUN: %clang_cc1 -xc++ -Wformat %s -verify +// RUN: %clang_cc1 -xobjective-c -Wformat -fblocks %s -verify +// RUN: %clang_cc1 -xobjective-c++ -Wformat -fblocks %s -verify +// RUN: %clang_cc1 -std=c23 -Wformat %s -pedantic -verify=expected,pedantic +// RUN: %clang_cc1 -xc++ -Wformat %s -pedantic -verify=expected,pedantic +// RUN: %clang_cc1 -xobjective-c -Wformat -fblocks -pedantic %s -verify=expected,pedantic + +__attribute__((__format__(__printf__, 1, 2))) +int printf(const char *, ...); +__attribute__((__format__(__scanf__, 1, 2))) +int scanf(const char *, ...); + +void f(void *vp, const void *cvp, char *cp, signed char *scp, int *ip) { + int arr[2]; + + printf("%p", cp); + printf("%p", cvp); + printf("%p", vp); + printf("%p", scp); + printf("%p", ip); // pedantic-warning {{format specifies type 'void *' but the argument has type 'int *'}} + printf("%p", arr); // pedantic-warning {{format specifies type 'void *' but the argument has type 'int *'}} + + scanf("%p", &vp); + scanf("%p", &cvp); + scanf("%p", (void *volatile*)&vp); + scanf("%p", (const void *volatile*)&cvp); + scanf("%p", &cp); // pedantic-warning {{format specifies type 'void **' but the argument has type 'char **'}} + scanf("%p", &ip); // pedantic-warning {{format specifies type 'void **' but the argument has type 'int **'}} + scanf("%p", &arr); // expected-warning {{format specifies type 'void **' but the argument has type 'int (*)[2]'}} + +#if !__is_identifier(nullptr) + typedef __typeof__(nullptr) nullptr_t; + nullptr_t np = nullptr; + nullptr_t *npp = &np; + + printf("%p", np); + scanf("%p", &np); // expected-warning {{format specifies type 'void **' but the argument has type 'nullptr_t *'}} + scanf("%p", &npp); // pedantic-warning {{format specifies type 'void **' but the argument has type 'nullptr_t **'}} +#endif + +#ifdef __OBJC__ + id i = 0; + void (^b)(void) = ^{}; + + printf("%p", i); // pedantic-warning {{format specifies type 'void *' but the argument has type 'id'}} + printf("%p", b); // pedantic-warning {{format specifies type 'void *' but the argument has type 'void (^)(void)'}} + scanf("%p", &i); // pedantic-warning {{format specifies type 'void **' but the argument has type 'id *'}} + scanf("%p", &b); // pedantic-warning {{format specifies type 'void **' but the argument has type 'void (^*)(void)'}} +#endif + +} diff --git a/clang/test/Sema/format-strings-pedantic.c b/clang/test/Sema/format-strings-pedantic.c index 76668978fadfe1..65387837e272a0 100644 --- a/clang/test/Sema/format-strings-pedantic.c +++ b/clang/test/Sema/format-strings-pedantic.c @@ -1,6 +1,7 @@ // RUN: %clang_cc1 -fsyntax-only -verify -Wno-format -Wformat-pedantic %s // RUN: %clang_cc1 -xobjective-c -fblocks -fsyntax-only -verify -Wno-format -Wformat-pedantic %s // RUN: %clang_cc1 -xc++ -fsyntax-only -verify -Wno-format -Wformat-pedantic %s +// RUN: %clang_cc1 -std=c23 -fsyntax-only -verify -Wno-format -Wformat-pedantic %s __attribute__((format(printf, 1, 2))) int printf(const char *restrict, ...); @@ -14,7 +15,7 @@ int main(void) { printf("%p", (id)0); // expected-warning {{format specifies type 'void *' but the argument has type 'id'}} #endif -#ifdef __cplusplus - printf("%p", nullptr); // expected-warning {{format specifies type 'void *' but the argument has type 'std::nullptr_t'}} +#if !__is_identifier(nullptr) + printf("%p", nullptr); #endif } diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html index 23657431233860..e6c955a5c0e255 100755 --- a/clang/www/cxx_dr_status.html +++ b/clang/www/cxx_dr_status.html @@ -4381,7 +4381,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2> <td><a href="https://cplusplus.github.io/CWG/issues/722.html">722</a></td> <td>CD2</td> <td>Can <TT>nullptr</TT> be passed to an ellipsis?</td> - <td class="unknown" align="center">Unknown</td> + <td class="unreleased" align="center">Clang 20</td> </tr> <tr id="726"> <td><a href="https://cplusplus.github.io/CWG/issues/726.html">726</a></td> _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits