Skip to content

Commit

Permalink
[Sema] Use underlying type of scoped enum for -Wformat diagnostics (#…
Browse files Browse the repository at this point in the history
…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 0b07b06effe5fdf779b75bb5ac6cf15e477cb0be)
  • Loading branch information
smeenai committed Oct 2, 2023
1 parent 33e14ec commit cd953d3
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 19 deletions.
6 changes: 2 additions & 4 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -475,10 +475,8 @@ Improvements to Clang's diagnostics
- ``-Wformat`` cast fix-its will now suggest ``static_cast`` instead of C-style casts
for C++ code.
- ``-Wformat`` will no longer suggest a no-op fix-it for fixing scoped enum format
warnings. Instead, it will suggest casting the enum object to the type specified
in the format string.
- Clang contexpr evaluator now displays notes as well as an error when a constructor
of a base class is not called in the constructor of its derived class.
warnings. Instead, it will suggest casting the enum object based on its
underlying type.

Bug Fixes in This Version
-------------------------
Expand Down
25 changes: 12 additions & 13 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11166,12 +11166,15 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
ImplicitMatch == ArgType::NoMatchTypeConfusion)
Match = ImplicitMatch;
assert(Match != ArgType::MatchPromotion);

// Look through unscoped enums to their underlying type.
bool IsEnum = false;
bool IsScopedEnum = false;
QualType IntendedTy = ExprTy;
if (auto EnumTy = ExprTy->getAs<EnumType>()) {
IntendedTy = EnumTy->getDecl()->getIntegerType();
if (EnumTy->isUnscopedEnumerationType()) {
ExprTy = EnumTy->getDecl()->getIntegerType();
ExprTy = IntendedTy;
// 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;
Expand All @@ -11183,7 +11186,6 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
// %C in an Objective-C context prints a unichar, not a wchar_t.
// If the argument is an integer of some kind, believe the %C and suggest
// a cast instead of changing the conversion specifier.
QualType IntendedTy = ExprTy;
if (isObjCContext() &&
FS.getConversionSpecifier().getKind() == ConversionSpecifier::CArg) {
if (ExprTy->isIntegralOrUnscopedEnumerationType() &&
Expand Down Expand Up @@ -11219,8 +11221,10 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
std::tie(CastTy, CastTyName) = shouldNotPrintDirectly(S.Context, IntendedTy, E);
if (!CastTy.isNull()) {
// %zi/%zu and %td/%tu are OK to use for NSInteger/NSUInteger of type int
// (long in ASTContext). Only complain to pedants.
if ((CastTyName == "NSInteger" || CastTyName == "NSUInteger") &&
// (long in ASTContext). Only complain to pedants or when they're the
// underlying type of a scoped enum (which always needs a cast).
if (!IsScopedEnum &&
(CastTyName == "NSInteger" || CastTyName == "NSUInteger") &&
(AT.isSizeT() || AT.isPtrdiffT()) &&
AT.matchesType(S.Context, CastTy))
Match = ArgType::NoMatchPedantic;
Expand Down Expand Up @@ -11275,20 +11279,15 @@ CheckPrintfHandler::checkFormatExpr(const analyze_printf::PrintfSpecifier &FS,
// should be printed as 'long' for 64-bit compatibility.)
// Rather than emitting a normal format/argument mismatch, we want to
// add a cast to the recommended type (and correct the format string
// if necessary).
// if necessary). We should also do so for scoped enumerations.
SmallString<16> CastBuf;
llvm::raw_svector_ostream CastFix(CastBuf);
CastFix << (S.LangOpts.CPlusPlus ? "static_cast<" : "(");
if (IsScopedEnum) {
CastFix << AT.getRepresentativeType(S.Context).getAsString(
S.Context.getPrintingPolicy());
} else {
IntendedTy.print(CastFix, S.Context.getPrintingPolicy());
}
IntendedTy.print(CastFix, S.Context.getPrintingPolicy());
CastFix << (S.LangOpts.CPlusPlus ? ">" : ")");

SmallVector<FixItHint,4> Hints;
if ((!AT.matchesType(S.Context, IntendedTy) && !IsScopedEnum) ||
if (AT.matchesType(S.Context, IntendedTy) != ArgType::Match ||
ShouldNotPrintDirectly)
Hints.push_back(FixItHint::CreateReplacement(SpecRange, os.str()));

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

if (ShouldNotPrintDirectly) {
if (ShouldNotPrintDirectly && !IsScopedEnum) {
// The expression has a type that should not be printed directly.
// We extract the name from the typedef because we don't want to show
// the underlying type in the diagnostic.
Expand Down
35 changes: 35 additions & 0 deletions clang/test/FixIt/format-darwin-enum-class.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// RUN: %clang_cc1 -triple x86_64-apple-darwin -fsyntax-only -verify -Wformat %s
// RUN: %clang_cc1 -triple x86_64-apple-darwin -fsyntax-only -fdiagnostics-parseable-fixits -Wformat %s 2>&1 | FileCheck %s

extern "C" int printf(const char * restrict, ...);

#if __LP64__
typedef long CFIndex;
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int CFIndex;
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif

enum class CFIndexEnum : CFIndex { One };
enum class NSIntegerEnum : NSInteger { Two };
enum class NSUIntegerEnum : NSUInteger { Three };

void f() {
printf("%d", CFIndexEnum::One); // expected-warning{{format specifies type 'int' but the argument has type 'CFIndexEnum'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%ld"
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"static_cast<long>("
// CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:32-[[@LINE-3]]:32}:")"

printf("%d", NSIntegerEnum::Two); // expected-warning{{format specifies type 'int' but the argument has type 'NSIntegerEnum'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%ld"
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"static_cast<long>("
// CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:34-[[@LINE-3]]:34}:")"

printf("%d", NSUIntegerEnum::Three); // expected-warning{{format specifies type 'int' but the argument has type 'NSUIntegerEnum'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:11-[[@LINE-1]]:13}:"%lu"
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:"static_cast<unsigned long>("
// CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:37-[[@LINE-3]]:37}:")"
}
20 changes: 18 additions & 2 deletions clang/test/FixIt/format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,33 @@ struct S {
N::E Type;
};

using uint32_t = unsigned;
enum class FixedE : uint32_t { Two };

void a(N::E NEVal, S *SPtr, S &SRef) {
printf("%d", N::E::One); // expected-warning{{format specifies type 'int' but the argument has type 'N::E'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:16-[[@LINE-1]]:16}:"static_cast<int>("
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:25-[[@LINE-2]]:25}:")"

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

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

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

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

printf("%d", NEVal); // expected-warning{{format specifies type 'int' but the argument has type 'N::E'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:16-[[@LINE-1]]:16}:"static_cast<int>("
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:21-[[@LINE-2]]:21}:")"
Expand Down Expand Up @@ -58,4 +70,8 @@ void a(N::E NEVal, S *SPtr, S &SRef) {
SRef.Type);
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:7-[[@LINE-1]]:7}:"static_cast<int>("
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:16-[[@LINE-2]]:16}:")"

printf("%u", FixedE::Two); //expected-warning{{format specifies type 'unsigned int' but the argument has type 'FixedE'}}
// CHECK: fix-it:"{{.*}}":{[[@LINE-1]]:16-[[@LINE-1]]:16}:"static_cast<uint32_t>("
// CHECK: fix-it:"{{.*}}":{[[@LINE-2]]:27-[[@LINE-2]]:27}:")"
}

0 comments on commit cd953d3

Please sign in to comment.