[clang] [Clang] CWG722: nullptr to ellipses (PR #104704)

Mital Ashok via cfe-commits cfe-commits at lists.llvm.org
Sun Aug 18 06:28:25 PDT 2024


https://github.com/MitalAshok created https://github.com/llvm/llvm-project/pull/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)


>From 1ecb1a5ddcd6256d46bc4293d0adbeb18ca590c1 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Sun, 23 Jul 2023 15:33:01 +0100
Subject: [PATCH] [Clang] CWG722: nullptr to ellipses

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)
---
 clang/docs/ReleaseNotes.rst               |  3 ++
 clang/lib/AST/FormatString.cpp            | 57 +++++++++++------------
 clang/lib/Sema/SemaExpr.cpp               | 13 ++++--
 clang/test/CXX/drs/cwg722.cpp             | 56 ++++++++++++++++++++++
 clang/test/CXX/drs/cwg7xx.cpp             |  2 +
 clang/test/Sema/format-pointer.c          | 53 +++++++++++++++++++++
 clang/test/Sema/format-strings-pedantic.c |  5 +-
 clang/www/cxx_dr_status.html              |  2 +-
 8 files changed, 154 insertions(+), 37 deletions(-)
 create mode 100644 clang/test/CXX/drs/cwg722.cpp
 create mode 100644 clang/test/Sema/format-pointer.c

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>



More information about the cfe-commits mailing list