[clang] [Clang] CWG722: nullptr to ellipses (PR #104704)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 18 06:28:58 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Mital Ashok (MitalAshok)
<details>
<summary>Changes</summary>
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)
---
Full diff: https://github.com/llvm/llvm-project/pull/104704.diff
8 Files Affected:
- (modified) clang/docs/ReleaseNotes.rst (+3)
- (modified) clang/lib/AST/FormatString.cpp (+26-31)
- (modified) clang/lib/Sema/SemaExpr.cpp (+10-3)
- (added) clang/test/CXX/drs/cwg722.cpp (+56)
- (modified) clang/test/CXX/drs/cwg7xx.cpp (+2)
- (added) clang/test/Sema/format-pointer.c (+53)
- (modified) clang/test/Sema/format-strings-pedantic.c (+3-2)
- (modified) clang/www/cxx_dr_status.html (+1-1)
``````````diff
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 11301e4c7dc4a2..6993c219dfcb79 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>
``````````
</details>
https://github.com/llvm/llvm-project/pull/104704
More information about the cfe-commits
mailing list