diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -89,6 +89,8 @@ Resolutions to C++ Defect Reports ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +- Implemented `DR722 `_ which promotes ``nullptr`` to ``void*`` + when passed to a C-style variadic function. C Language Changes ------------------ diff --git a/clang/lib/AST/FormatString.cpp b/clang/lib/AST/FormatString.cpp --- a/clang/lib/AST/FormatString.cpp +++ b/clang/lib/AST/FormatString.cpp @@ -481,20 +481,8 @@ } case CStrTy: { - const PointerType *PT = argTy->getAs(); - if (!PT) - return NoMatch; - QualType pointeeTy = PT->getPointeeType(); - if (const BuiltinType *BT = pointeeTy->getAs()) - switch (BT->getKind()) { - case BuiltinType::Char_U: - case BuiltinType::UChar: - case BuiltinType::Char_S: - case BuiltinType::SChar: - return Match; - default: - break; - } + if (const auto *PT = argTy->getAs(); PT && PT->getPointeeType()->isCharType()) + return Match; return NoMatch; } @@ -529,15 +517,21 @@ } case CPointerTy: - if (argTy->isVoidPointerType()) { - return Match; - } if (argTy->isPointerType() || argTy->isObjCObjectPointerType() || - argTy->isBlockPointerType() || argTy->isNullPtrType()) { + if (const auto *PT = argTy->getAs()) { + QualType PointeeTy = PT->getPointeeType(); + if (PointeeTy->isVoidType() || (!Ptr && PointeeTy->isCharType())) + return Match; return NoMatchPedantic; - } else { - return NoMatch; } + if (argTy->isNullPtrType()) + return MatchPromotion; + + if (argTy->isObjCObjectPointerType() || argTy->isBlockPointerType()) + return NoMatchPedantic; + + return NoMatch; + case ObjCPointerTy: { if (argTy->getAs() || argTy->getAs()) diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -923,6 +923,13 @@ E = Temp.get(); } + // C++ [expr.call]p7, per DR722: + // An argument that has (possibly cv-qualified) type std::nullptr_t is + // converted to void* ([conv.ptr]). + // (This does not apply to C2x nullptr) + if (getLangOpts().CPlusPlus && E->getType()->isNullPtrType()) + E = ImpCastExprToType(E, Context.VoidPtrTy, CK_NullToPointer).get(); + return E; } @@ -936,9 +943,9 @@ // 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; @@ -17277,65 +17284,25 @@ << TInfo->getType() << TInfo->getTypeLoc().getSourceRange(); } - - // Check for va_arg where arguments of the given type will be promoted - // (i.e. this va_arg is guaranteed to have undefined behavior). - QualType PromoteType; - if (Context.isPromotableIntegerType(TInfo->getType())) { - PromoteType = Context.getPromotedIntegerType(TInfo->getType()); - // [cstdarg.syn]p1 defers the C++ behavior to what the C standard says, - // and C2x 7.16.1.1p2 says, in part: - // If type is not compatible with the type of the actual next argument - // (as promoted according to the default argument promotions), the - // behavior is undefined, except for the following cases: - // - both types are pointers to qualified or unqualified versions of - // compatible types; - // - one type is a signed integer type, the other type is the - // corresponding unsigned integer type, and the value is - // representable in both types; - // - one type is pointer to qualified or unqualified void and the - // other is a pointer to a qualified or unqualified character type. - // Given that type compatibility is the primary requirement (ignoring - // qualifications), you would think we could call typesAreCompatible() - // directly to test this. However, in C++, that checks for *same type*, - // which causes false positives when passing an enumeration type to - // va_arg. Instead, get the underlying type of the enumeration and pass - // that. - QualType UnderlyingType = TInfo->getType(); - if (const auto *ET = UnderlyingType->getAs()) - UnderlyingType = ET->getDecl()->getIntegerType(); - if (Context.typesAreCompatible(PromoteType, UnderlyingType, - /*CompareUnqualified*/ true)) - PromoteType = QualType(); - - // If the types are still not compatible, we need to test whether the - // promoted type and the underlying type are the same except for - // signedness. Ask the AST for the correctly corresponding type and see - // if that's compatible. - if (!PromoteType.isNull() && !UnderlyingType->isBooleanType() && - PromoteType->isUnsignedIntegerType() != - UnderlyingType->isUnsignedIntegerType()) { - UnderlyingType = - UnderlyingType->isUnsignedIntegerType() - ? Context.getCorrespondingSignedType(UnderlyingType) - : Context.getCorrespondingUnsignedType(UnderlyingType); - if (Context.typesAreCompatible(PromoteType, UnderlyingType, - /*CompareUnqualified*/ true)) - PromoteType = QualType(); - } - } - if (TInfo->getType()->isSpecificBuiltinType(BuiltinType::Float)) - PromoteType = Context.DoubleTy; - if (!PromoteType.isNull()) - DiagRuntimeBehavior(TInfo->getTypeLoc().getBeginLoc(), E, - PDiag(diag::warn_second_parameter_to_va_arg_never_compatible) - << TInfo->getType() - << PromoteType - << TInfo->getTypeLoc().getSourceRange()); } QualType T = TInfo->getType().getNonLValueExprType(Context); - return new (Context) VAArgExpr(BuiltinLoc, E, TInfo, RPLoc, T, IsMS); + auto *ResultExpr = new (Context) VAArgExpr(BuiltinLoc, E, TInfo, RPLoc, T, IsMS); + + // Check for va_arg where arguments of the given type will be promoted + // (i.e. this va_arg is guaranteed to have undefined behavior). + ExprResult PromotedExpr = DefaultArgumentPromotion(ResultExpr); + QualType PromoteType = PromotedExpr.get()->getType(); + + if (!Context.typesAreCompatible(PromoteType, ResultExpr->getType(), true)) { + DiagRuntimeBehavior(TInfo->getTypeLoc().getBeginLoc(), E, + PDiag(diag::warn_second_parameter_to_va_arg_never_compatible) + << TInfo->getType() + << PromoteType + << TInfo->getTypeLoc().getSourceRange()); + } + + return ResultExpr; } ExprResult Sema::ActOnGNUNullExpr(SourceLocation TokenLoc) { diff --git a/clang/test/CXX/drs/dr7xx.cpp b/clang/test/CXX/drs/dr7xx.cpp --- a/clang/test/CXX/drs/dr7xx.cpp +++ b/clang/test/CXX/drs/dr7xx.cpp @@ -53,6 +53,15 @@ #endif } +namespace dr722 { // dr722: 18 +#if __cplusplus >= 201103L + int x = __builtin_printf("%p", nullptr); + void f(__builtin_va_list args) { + __builtin_va_arg(args, decltype(nullptr)); // expected-warning {{second argument to 'va_arg' is of promotable type 'decltype(nullptr)' (aka 'std::nullptr_t'); this va_arg has undefined behavior because arguments will be promoted to 'void *'}} + } +#endif +} + namespace dr727 { // dr727: partial struct A { template struct C; // expected-note 6{{here}} diff --git a/clang/test/CodeGen/xcore-abi.c b/clang/test/CodeGen/xcore-abi.c --- a/clang/test/CodeGen/xcore-abi.c +++ b/clang/test/CodeGen/xcore-abi.c @@ -77,6 +77,7 @@ // CHECK: call void @f(ptr noundef [[V5]]) int* v6 = va_arg (ap, int[4]); // an unusual aggregate type + // expected-warning@-1{{second argument to 'va_arg' is of promotable type 'int[4]'}} f(v6); // CHECK: [[I:%[a-z0-9]+]] = load ptr, ptr [[AP]] // CHECK: [[P:%[a-z0-9]+]] = load ptr, ptr [[I]] diff --git a/clang/test/Sema/format-pointer.c b/clang/test/Sema/format-pointer.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/format-pointer.c @@ -0,0 +1,53 @@ +// RUN: %clang_cc1 -xc -Wformat %s -verify +// RUN: %clang_cc1 -xc -Wformat -std=c2x %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 -xc -std=c2x -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 --- 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=c2x -fsyntax-only -verify -Wno-format -Wformat-pedantic %s __attribute__((format(printf, 1, 2))) int printf(const char *restrict, ...); @@ -14,7 +15,7 @@ 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/test/SemaCXX/format-strings-0x-nopedantic.cpp b/clang/test/SemaCXX/format-strings-0x-nopedantic.cpp --- a/clang/test/SemaCXX/format-strings-0x-nopedantic.cpp +++ b/clang/test/SemaCXX/format-strings-0x-nopedantic.cpp @@ -5,6 +5,7 @@ extern int printf(const char *restrict, ...); } -void f(char *c) { +void f(char *c, int *q) { printf("%p", c); + printf("%p", q); } diff --git a/clang/test/SemaCXX/varargs.cpp b/clang/test/SemaCXX/varargs.cpp --- a/clang/test/SemaCXX/varargs.cpp +++ b/clang/test/SemaCXX/varargs.cpp @@ -55,6 +55,16 @@ (void)__builtin_va_arg(ap, unsigned int); (void)__builtin_va_arg(ap, bool); // expected-warning {{second argument to 'va_arg' is of promotable type 'bool'; this va_arg has undefined behavior because arguments will be promoted to 'int'}} + + (void)__builtin_va_arg(ap, float); // expected-warning {{second argument to 'va_arg' is of promotable type 'float'; this va_arg has undefined behavior because arguments will be promoted to 'double'}} + (void)__builtin_va_arg(ap, __fp16); // expected-warning {{second argument to 'va_arg' is of promotable type '__fp16'; this va_arg has undefined behavior because arguments will be promoted to 'double'}} + +#if __cplusplus >= 201103L + (void)__builtin_va_arg(ap, decltype(nullptr)); // expected-warning {{second argument to 'va_arg' is of promotable type 'decltype(nullptr)' (aka 'std::nullptr_t'); this va_arg has undefined behavior because arguments will be promoted to 'void *'}} +#endif + + (void)__builtin_va_arg(ap, int[3]); // expected-warning {{second argument to 'va_arg' is of promotable type 'int[3]'; this va_arg has undefined behavior because arguments will be promoted to 'int *'}} + (void)__builtin_va_arg(ap, const int[3]); // expected-warning {{second argument to 'va_arg' is of promotable type 'const int[3]'; this va_arg has undefined behavior because arguments will be promoted to 'const int *'}} } #if __cplusplus >= 201103L diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html --- a/clang/www/cxx_dr_status.html +++ b/clang/www/cxx_dr_status.html @@ -4373,7 +4373,7 @@ 722 CD2 Can nullptr be passed to an ellipsis? - Unknown + Clang 18 726