Skip to content

Commit 0b07b06

Browse files
authored
[Sema] Use underlying type of scoped enum for -Wformat diagnostics (llvm#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.
1 parent e0f86ca commit 0b07b06

File tree

4 files changed

+68
-15
lines changed

4 files changed

+68
-15
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ Improvements to Clang's diagnostics
213213
(`#54678: <https://github.com/llvm/llvm-project/issues/54678>`_).
214214
- Clang now prints its 'note' diagnostic in cyan instead of black, to be more compatible
215215
with terminals with dark background colors. This is also more consistent with GCC.
216+
- The fix-it emitted by ``-Wformat`` for scoped enumerations now take the
217+
enumeration's underlying type into account instead of suggesting a type just
218+
based on the format string specifier being used.
216219

217220
Bug Fixes in This Version
218221
-------------------------

clang/lib/Sema/SemaChecking.cpp

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11356,12 +11356,15 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
1135611356
ImplicitMatch == ArgType::NoMatchTypeConfusion)
1135711357
Match = ImplicitMatch;
1135811358
assert(Match != ArgType::MatchPromotion);
11359+
1135911360
// Look through unscoped enums to their underlying type.
1136011361
bool IsEnum = false;
1136111362
bool IsScopedEnum = false;
11363+
QualType IntendedTy = ExprTy;
1136211364
if (auto EnumTy = ExprTy->getAs<EnumType>()) {
11365+
IntendedTy = EnumTy->getDecl()->getIntegerType();
1136311366
if (EnumTy->isUnscopedEnumerationType()) {
11364-
ExprTy = EnumTy->getDecl()->getIntegerType();
11367+
ExprTy = IntendedTy;
1136511368
// This controls whether we're talking about the underlying type or not,
1136611369
// which we only want to do when it's an unscoped enum.
1136711370
IsEnum = true;
@@ -11373,7 +11376,6 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
1137311376
// %C in an Objective-C context prints a unichar, not a wchar_t.
1137411377
// If the argument is an integer of some kind, believe the %C and suggest
1137511378
// a cast instead of changing the conversion specifier.
11376-
QualType IntendedTy = ExprTy;
1137711379
if (isObjCContext() &&
1137811380
FS.getConversionSpecifier().getKind() == ConversionSpecifier::CArg) {
1137911381
if (ExprTy->isIntegralOrUnscopedEnumerationType() &&
@@ -11409,8 +11411,10 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
1140911411
std::tie(CastTy, CastTyName) = shouldNotPrintDirectly(S.Context, IntendedTy, E);
1141011412
if (!CastTy.isNull()) {
1141111413
// %zi/%zu and %td/%tu are OK to use for NSInteger/NSUInteger of type int
11412-
// (long in ASTContext). Only complain to pedants.
11413-
if ((CastTyName == "NSInteger" || CastTyName == "NSUInteger") &&
11414+
// (long in ASTContext). Only complain to pedants or when they're the
11415+
// underlying type of a scoped enum (which always needs a cast).
11416+
if (!IsScopedEnum &&
11417+
(CastTyName == "NSInteger" || CastTyName == "NSUInteger") &&
1141411418
(AT.isSizeT() || AT.isPtrdiffT()) &&
1141511419
AT.matchesType(S.Context, CastTy))
1141611420
Match = ArgType::NoMatchPedantic;
@@ -11465,20 +11469,15 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
1146511469
// should be printed as 'long' for 64-bit compatibility.)
1146611470
// Rather than emitting a normal format/argument mismatch, we want to
1146711471
// add a cast to the recommended type (and correct the format string
11468-
// if necessary).
11472+
// if necessary). We should also do so for scoped enumerations.
1146911473
SmallString<16> CastBuf;
1147011474
llvm::raw_svector_ostream CastFix(CastBuf);
1147111475
CastFix << (S.LangOpts.CPlusPlus ? "static_cast<" : "(");
11472-
if (IsScopedEnum) {
11473-
CastFix << AT.getRepresentativeType(S.Context).getAsString(
11474-
S.Context.getPrintingPolicy());
11475-
} else {
11476-
IntendedTy.print(CastFix, S.Context.getPrintingPolicy());
11477-
}
11476+
IntendedTy.print(CastFix, S.Context.getPrintingPolicy());
1147811477
CastFix << (S.LangOpts.CPlusPlus ? ">" : ")");
1147911478

1148011479
SmallVector<FixItHint,4> Hints;
11481-
if ((!AT.matchesType(S.Context, IntendedTy) && !IsScopedEnum) ||
11480+
if (AT.matchesType(S.Context, IntendedTy) != ArgType::Match ||
1148211481
ShouldNotPrintDirectly)
1148311482
Hints.push_back(FixItHint::CreateReplacement(SpecRange, os.str()));
1148411483

@@ -11506,7 +11505,7 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
1150611505
Hints.push_back(FixItHint::CreateInsertion(After, ")"));
1150711506
}
1150811507

11509-
if (ShouldNotPrintDirectly) {
11508+
if (ShouldNotPrintDirectly && !IsScopedEnum) {
1151011509
// The expression has a type that should not be printed directly.
1151111510
// We extract the name from the typedef because we don't want to show
1151211511
// the underlying type in the diagnostic.
Lines changed: 35 additions & 0 deletions
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

Lines changed: 18 additions & 2 deletions
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)