Skip to content

Commit 69c8c96

Browse files
smeenaitru
authored andcommitted
[Sema] Use underlying type of scoped enum for -Wformat diagnostics (#67378)
Right now, `-Wformat` for a scoped enum will suggest a cast based on the format specifier being used. This can lead to incorrect results, e.g. attempting to format a scoped enum with `%s` would suggest casting to `char *` instead of fixing the specifier. Change the logic to treat the scoped enum's underlying type as the intended type to be printed, and suggest format specifier changes and casts based on that. (cherry picked from commit 0b07b06)
1 parent b2417f5 commit 69c8c96

File tree

4 files changed

+67
-19
lines changed

4 files changed

+67
-19
lines changed

clang/docs/ReleaseNotes.rst

+2-4
Original file line numberDiff line numberDiff line change
@@ -475,10 +475,8 @@ Improvements to Clang's diagnostics
475475
- ``-Wformat`` cast fix-its will now suggest ``static_cast`` instead of C-style casts
476476
for C++ code.
477477
- ``-Wformat`` will no longer suggest a no-op fix-it for fixing scoped enum format
478-
warnings. Instead, it will suggest casting the enum object to the type specified
479-
in the format string.
480-
- Clang contexpr evaluator now displays notes as well as an error when a constructor
481-
of a base class is not called in the constructor of its derived class.
478+
warnings. Instead, it will suggest casting the enum object based on its
479+
underlying type.
482480

483481
Bug Fixes in This Version
484482
-------------------------

clang/lib/Sema/SemaChecking.cpp

+12-13
Original file line numberDiff line numberDiff line change
@@ -11166,12 +11166,15 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
1116611166
ImplicitMatch == ArgType::NoMatchTypeConfusion)
1116711167
Match = ImplicitMatch;
1116811168
assert(Match != ArgType::MatchPromotion);
11169+
1116911170
// Look through unscoped enums to their underlying type.
1117011171
bool IsEnum = false;
1117111172
bool IsScopedEnum = false;
11173+
QualType IntendedTy = ExprTy;
1117211174
if (auto EnumTy = ExprTy->getAs<EnumType>()) {
11175+
IntendedTy = EnumTy->getDecl()->getIntegerType();
1117311176
if (EnumTy->isUnscopedEnumerationType()) {
11174-
ExprTy = EnumTy->getDecl()->getIntegerType();
11177+
ExprTy = IntendedTy;
1117511178
// This controls whether we're talking about the underlying type or not,
1117611179
// which we only want to do when it's an unscoped enum.
1117711180
IsEnum = true;
@@ -11183,7 +11186,6 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
1118311186
// %C in an Objective-C context prints a unichar, not a wchar_t.
1118411187
// If the argument is an integer of some kind, believe the %C and suggest
1118511188
// a cast instead of changing the conversion specifier.
11186-
QualType IntendedTy = ExprTy;
1118711189
if (isObjCContext() &&
1118811190
FS.getConversionSpecifier().getKind() == ConversionSpecifier::CArg) {
1118911191
if (ExprTy->isIntegralOrUnscopedEnumerationType() &&
@@ -11219,8 +11221,10 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
1121911221
std::tie(CastTy, CastTyName) = shouldNotPrintDirectly(S.Context, IntendedTy, E);
1122011222
if (!CastTy.isNull()) {
1122111223
// %zi/%zu and %td/%tu are OK to use for NSInteger/NSUInteger of type int
11222-
// (long in ASTContext). Only complain to pedants.
11223-
if ((CastTyName == "NSInteger" || CastTyName == "NSUInteger") &&
11224+
// (long in ASTContext). Only complain to pedants or when they're the
11225+
// underlying type of a scoped enum (which always needs a cast).
11226+
if (!IsScopedEnum &&
11227+
(CastTyName == "NSInteger" || CastTyName == "NSUInteger") &&
1122411228
(AT.isSizeT() || AT.isPtrdiffT()) &&
1122511229
AT.matchesType(S.Context, CastTy))
1122611230
Match = ArgType::NoMatchPedantic;
@@ -11275,20 +11279,15 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
1127511279
// should be printed as 'long' for 64-bit compatibility.)
1127611280
// Rather than emitting a normal format/argument mismatch, we want to
1127711281
// add a cast to the recommended type (and correct the format string
11278-
// if necessary).
11282+
// if necessary). We should also do so for scoped enumerations.
1127911283
SmallString<16> CastBuf;
1128011284
llvm::raw_svector_ostream CastFix(CastBuf);
1128111285
CastFix << (S.LangOpts.CPlusPlus ? "static_cast<" : "(");
11282-
if (IsScopedEnum) {
11283-
CastFix << AT.getRepresentativeType(S.Context).getAsString(
11284-
S.Context.getPrintingPolicy());
11285-
} else {
11286-
IntendedTy.print(CastFix, S.Context.getPrintingPolicy());
11287-
}
11286+
IntendedTy.print(CastFix, S.Context.getPrintingPolicy());
1128811287
CastFix << (S.LangOpts.CPlusPlus ? ">" : ")");
1128911288

1129011289
SmallVector<FixItHint,4> Hints;
11291-
if ((!AT.matchesType(S.Context, IntendedTy) && !IsScopedEnum) ||
11290+
if (AT.matchesType(S.Context, IntendedTy) != ArgType::Match ||
1129211291
ShouldNotPrintDirectly)
1129311292
Hints.push_back(FixItHint::CreateReplacement(SpecRange, os.str()));
1129411293

@@ -11316,7 +11315,7 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
1131611315
Hints.push_back(FixItHint::CreateInsertion(After, ")"));
1131711316
}
1131811317

11319-
if (ShouldNotPrintDirectly) {
11318+
if (ShouldNotPrintDirectly && !IsScopedEnum) {
1132011319
// The expression has a type that should not be printed directly.
1132111320
// We extract the name from the typedef because we don't want to show
1132211321
// the underlying type in the diagnostic.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// RUN: %clang_cc1 -triple x86_64-apple-darwin -fsyntax-only -verify -Wformat %s
2+
// RUN: %clang_cc1 -triple x86_64-apple-darwin -fsyntax-only -fdiagnostics-parseable-fixits -Wformat %s 2>&1 | FileCheck %s
3+
4+
extern "C" int printf(const char * restrict, ...);
5+
6+
#if __LP64__
7+
typedef long CFIndex;
8+
typedef long NSInteger;
9+
typedef unsigned long NSUInteger;
10+
#else
11+
typedef int CFIndex;
12+
typedef int NSInteger;
13+
typedef unsigned int NSUInteger;
14+
#endif
15+
16+
enum class CFIndexEnum : CFIndex { One };
17+
enum class NSIntegerEnum : NSInteger { Two };
18+
enum class NSUIntegerEnum : NSUInteger { Three };
19+
20+
void f() {
21+
printf("%d", CFIndexEnum::One); // expected-warning{{format specifies type 'int' but the argument has type 'CFIndexEnum'}}
22+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%ld"
23+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"static_cast<long>("
24+
// CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:32-[[@LINE-3]]:32}:")"
25+
26+
printf("%d", NSIntegerEnum::Two); // expected-warning{{format specifies type 'int' but the argument has type 'NSIntegerEnum'}}
27+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%ld"
28+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"static_cast<long>("
29+
// CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:34-[[@LINE-3]]:34}:")"
30+
31+
printf("%d", NSUIntegerEnum::Three); // expected-warning{{format specifies type 'int' but the argument has type 'NSUIntegerEnum'}}
32+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%lu"
33+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"static_cast<unsigned long>("
34+
// CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:37-[[@LINE-3]]:37}:")"
35+
}

clang/test/FixIt/format.cpp

+18-2
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,33 @@ struct S {
1212
N::E Type;
1313
};
1414

15+
using uint32_t = unsigned;
16+
enum class FixedE : uint32_t { Two };
17+
1518
void a(N::E NEVal, S *SPtr, S &SRef) {
1619
printf("%d", N::E::One); // expected-warning{{format specifies type 'int' but the argument has type 'N::E'}}
1720
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:16-[[@LINE-1]]:16}:"static_cast<int>("
1821
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:25-[[@LINE-2]]:25}:")"
1922

2023
printf("%hd", N::E::One); // expected-warning{{format specifies type 'short' but the argument has type 'N::E'}}
21-
// CHECK: "static_cast<short>("
24+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:14}:"%d"
25+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:17-[[@LINE-2]]:17}:"static_cast<int>("
26+
// CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:26-[[@LINE-3]]:26}:")"
2227

2328
printf("%hu", N::E::One); // expected-warning{{format specifies type 'unsigned short' but the argument has type 'N::E'}}
24-
// CHECK: "static_cast<unsigned short>("
29+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:14}:"%d"
30+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:17-[[@LINE-2]]:17}:"static_cast<int>("
31+
// CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:26-[[@LINE-3]]:26}:")"
2532

2633
LOG("%d", N::E::One); // expected-warning{{format specifies type 'int' but the argument has type 'N::E'}}
2734
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:13-[[@LINE-1]]:13}:"static_cast<int>("
2835
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:22-[[@LINE-2]]:22}:")"
2936

37+
LOG("%s", N::E::One); // expected-warning{{format specifies type 'char *' but the argument has type 'N::E'}}
38+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:8-[[@LINE-1]]:10}:"%d"
39+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:13-[[@LINE-2]]:13}:"static_cast<int>("
40+
// CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:22-[[@LINE-3]]:22}:")"
41+
3042
printf("%d", NEVal); // expected-warning{{format specifies type 'int' but the argument has type 'N::E'}}
3143
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:16-[[@LINE-1]]:16}:"static_cast<int>("
3244
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:21-[[@LINE-2]]:21}:")"
@@ -58,4 +70,8 @@ void a(N::E NEVal, S *SPtr, S &SRef) {
5870
SRef.Type);
5971
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:7-[[@LINE-1]]:7}:"static_cast<int>("
6072
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:")"
73+
74+
printf("%u", FixedE::Two); //expected-warning{{format specifies type 'unsigned int' but the argument has type 'FixedE'}}
75+
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:16-[[@LINE-1]]:16}:"static_cast<uint32_t>("
76+
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:27-[[@LINE-2]]:27}:")"
6177
}

0 commit comments

Comments
 (0)