Diagnose incorrect use of scoped enumerations in format strings

Scoped enumerations in C++ do not undergo conversion to their
underlying type as part of default argument promotion, and so these
uses are UB. GCC correctly diagnoses them, and now Clang matches.

Fixes https://github.com/llvm/llvm-project/issues/38717
This commit is contained in:
Aaron Ballman
2023-06-16 15:01:42 -04:00
parent e872e162a3
commit 3632e2f517
4 changed files with 43 additions and 7 deletions

View File

@@ -513,6 +513,11 @@ Bug Fixes in This Version
(`#50244 <https://github.com/llvm/llvm-project/issues/50244>_`).
- Apply ``-fmacro-prefix-map`` to anonymous tags in template arguments
(`#63219 <https://github.com/llvm/llvm-project/issues/63219>`_).
- Clang now properly diagnoses format string mismatches involving scoped
enumeration types. A scoped enumeration type is not promoted to an integer
type by the default argument promotions, and thus this is UB. Clang's
behavior now matches GCC's behavior in C++.
(`#38717 <https://github.com/llvm/llvm-project/issues/38717>_`).
Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -351,10 +351,12 @@ ArgType::matchesType(ASTContext &C, QualType argTy) const {
case AnyCharTy: {
if (const auto *ETy = argTy->getAs<EnumType>()) {
// If the enum is incomplete we know nothing about the underlying type.
// Assume that it's 'int'.
// Assume that it's 'int'. Do not use the underlying type for a scoped
// enumeration.
if (!ETy->getDecl()->isComplete())
return NoMatch;
argTy = ETy->getDecl()->getIntegerType();
if (ETy->isUnscopedEnumerationType())
argTy = ETy->getDecl()->getIntegerType();
}
if (const auto *BT = argTy->getAs<BuiltinType>()) {
@@ -391,10 +393,11 @@ ArgType::matchesType(ASTContext &C, QualType argTy) const {
case SpecificTy: {
if (const EnumType *ETy = argTy->getAs<EnumType>()) {
// If the enum is incomplete we know nothing about the underlying type.
// Assume that it's 'int'.
// Assume that it's 'int'. Do not use the underlying type for a scoped
// enumeration as that needs an exact match.
if (!ETy->getDecl()->isComplete())
argTy = C.IntTy;
else
else if (ETy->isUnscopedEnumerationType())
argTy = ETy->getDecl()->getIntegerType();
}
argTy = C.getCanonicalType(argTy).getUnqualifiedType();

View File

@@ -10564,11 +10564,15 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
ImplicitMatch == ArgType::NoMatchTypeConfusion)
Match = ImplicitMatch;
assert(Match != ArgType::MatchPromotion);
// Look through enums to their underlying type.
// Look through unscoped enums to their underlying type.
bool IsEnum = false;
if (auto EnumTy = ExprTy->getAs<EnumType>()) {
ExprTy = EnumTy->getDecl()->getIntegerType();
IsEnum = true;
if (EnumTy->isUnscopedEnumerationType()) {
ExprTy = EnumTy->getDecl()->getIntegerType();
// This controls whether we're talking about the underlying type or not,
// which we only want to do when it's an unscoped enum.
IsEnum = true;
}
}
// %C in an Objective-C context prints a unichar, not a wchar_t.

View File

@@ -213,4 +213,28 @@ void f() {
}
namespace ScopedEnumerations {
enum class Scoped1 { One };
enum class Scoped2 : unsigned short { Two };
void f(Scoped1 S1, Scoped2 S2) {
printf("%hhd", S1); // expected-warning {{format specifies type 'char' but the argument has type 'Scoped1'}}
printf("%hd", S1); // expected-warning {{format specifies type 'short' but the argument has type 'Scoped1'}}
printf("%d", S1); // expected-warning {{format specifies type 'int' but the argument has type 'Scoped1'}}
printf("%hhd", S2); // expected-warning {{format specifies type 'char' but the argument has type 'Scoped2'}}
printf("%hd", S2); // expected-warning {{format specifies type 'short' but the argument has type 'Scoped2'}}
printf("%d", S2); // expected-warning {{format specifies type 'int' but the argument has type 'Scoped2'}}
scanf("%hhd", &S1); // expected-warning {{format specifies type 'char *' but the argument has type 'Scoped1 *'}}
scanf("%hd", &S1); // expected-warning {{format specifies type 'short *' but the argument has type 'Scoped1 *'}}
scanf("%d", &S1); // expected-warning {{format specifies type 'int *' but the argument has type 'Scoped1 *'}}
scanf("%hhd", &S2); // expected-warning {{format specifies type 'char *' but the argument has type 'Scoped2 *'}}
scanf("%hd", &S2); // expected-warning {{format specifies type 'short *' but the argument has type 'Scoped2 *'}}
scanf("%d", &S2); // expected-warning {{format specifies type 'int *' but the argument has type 'Scoped2 *'}}
}
}
#endif