Skip to content

Commit 3632e2f

Browse files
committed
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 llvm#38717
1 parent e872e16 commit 3632e2f

File tree

4 files changed

+43
-7
lines changed

4 files changed

+43
-7
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,11 @@ Bug Fixes in This Version
513513
(`#50244 <https://github.com/llvm/llvm-project/issues/50244>_`).
514514
- Apply ``-fmacro-prefix-map`` to anonymous tags in template arguments
515515
(`#63219 <https://github.com/llvm/llvm-project/issues/63219>`_).
516+
- Clang now properly diagnoses format string mismatches involving scoped
517+
enumeration types. A scoped enumeration type is not promoted to an integer
518+
type by the default argument promotions, and thus this is UB. Clang's
519+
behavior now matches GCC's behavior in C++.
520+
(`#38717 <https://github.com/llvm/llvm-project/issues/38717>_`).
516521

517522
Bug Fixes to Compiler Builtins
518523
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

clang/lib/AST/FormatString.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -351,10 +351,12 @@ ArgType::matchesType(ASTContext &C, QualType argTy) const {
351351
case AnyCharTy: {
352352
if (const auto *ETy = argTy->getAs<EnumType>()) {
353353
// If the enum is incomplete we know nothing about the underlying type.
354-
// Assume that it's 'int'.
354+
// Assume that it's 'int'. Do not use the underlying type for a scoped
355+
// enumeration.
355356
if (!ETy->getDecl()->isComplete())
356357
return NoMatch;
357-
argTy = ETy->getDecl()->getIntegerType();
358+
if (ETy->isUnscopedEnumerationType())
359+
argTy = ETy->getDecl()->getIntegerType();
358360
}
359361

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

clang/lib/Sema/SemaChecking.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10564,11 +10564,15 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
1056410564
ImplicitMatch == ArgType::NoMatchTypeConfusion)
1056510565
Match = ImplicitMatch;
1056610566
assert(Match != ArgType::MatchPromotion);
10567-
// Look through enums to their underlying type.
10567+
// Look through unscoped enums to their underlying type.
1056810568
bool IsEnum = false;
1056910569
if (auto EnumTy = ExprTy->getAs<EnumType>()) {
10570-
ExprTy = EnumTy->getDecl()->getIntegerType();
10571-
IsEnum = true;
10570+
if (EnumTy->isUnscopedEnumerationType()) {
10571+
ExprTy = EnumTy->getDecl()->getIntegerType();
10572+
// This controls whether we're talking about the underlying type or not,
10573+
// which we only want to do when it's an unscoped enum.
10574+
IsEnum = true;
10575+
}
1057210576
}
1057310577

1057410578
// %C in an Objective-C context prints a unichar, not a wchar_t.

clang/test/SemaCXX/format-strings.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,28 @@ void f() {
213213

214214

215215
}
216+
217+
namespace ScopedEnumerations {
218+
enum class Scoped1 { One };
219+
enum class Scoped2 : unsigned short { Two };
220+
221+
void f(Scoped1 S1, Scoped2 S2) {
222+
printf("%hhd", S1); // expected-warning {{format specifies type 'char' but the argument has type 'Scoped1'}}
223+
printf("%hd", S1); // expected-warning {{format specifies type 'short' but the argument has type 'Scoped1'}}
224+
printf("%d", S1); // expected-warning {{format specifies type 'int' but the argument has type 'Scoped1'}}
225+
226+
printf("%hhd", S2); // expected-warning {{format specifies type 'char' but the argument has type 'Scoped2'}}
227+
printf("%hd", S2); // expected-warning {{format specifies type 'short' but the argument has type 'Scoped2'}}
228+
printf("%d", S2); // expected-warning {{format specifies type 'int' but the argument has type 'Scoped2'}}
229+
230+
scanf("%hhd", &S1); // expected-warning {{format specifies type 'char *' but the argument has type 'Scoped1 *'}}
231+
scanf("%hd", &S1); // expected-warning {{format specifies type 'short *' but the argument has type 'Scoped1 *'}}
232+
scanf("%d", &S1); // expected-warning {{format specifies type 'int *' but the argument has type 'Scoped1 *'}}
233+
234+
scanf("%hhd", &S2); // expected-warning {{format specifies type 'char *' but the argument has type 'Scoped2 *'}}
235+
scanf("%hd", &S2); // expected-warning {{format specifies type 'short *' but the argument has type 'Scoped2 *'}}
236+
scanf("%d", &S2); // expected-warning {{format specifies type 'int *' but the argument has type 'Scoped2 *'}}
237+
}
238+
}
239+
216240
#endif

0 commit comments

Comments
 (0)