diff --git a/clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.cpp index 7250e4ccb8c69..57e1f744fcd7d 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.cpp @@ -7,6 +7,8 @@ //===----------------------------------------------------------------------===// #include "UnintendedCharOstreamOutputCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" #include "clang/AST/Type.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" @@ -35,10 +37,14 @@ AST_MATCHER(Type, isChar) { UnintendedCharOstreamOutputCheck::UnintendedCharOstreamOutputCheck( StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context), CastTypeName(Options.get("CastTypeName")) { -} + : ClangTidyCheck(Name, Context), + AllowedTypes(utils::options::parseStringList( + Options.get("AllowedTypes", "unsigned char;signed char"))), + CastTypeName(Options.get("CastTypeName")) {} void UnintendedCharOstreamOutputCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "AllowedTypes", + utils::options::serializeStringList(AllowedTypes)); if (CastTypeName.has_value()) Options.store(Opts, "CastTypeName", CastTypeName.value()); } @@ -50,13 +56,20 @@ void UnintendedCharOstreamOutputCheck::registerMatchers(MatchFinder *Finder) { // with char / unsigned char / signed char classTemplateSpecializationDecl( hasTemplateArgument(0, refersToType(isChar())))); + auto IsDeclRefExprFromAllowedTypes = declRefExpr(to(varDecl( + hasType(matchers::matchesAnyListedTypeName(AllowedTypes, false))))); + auto IsExplicitCastExprFromAllowedTypes = explicitCastExpr(hasDestinationType( + matchers::matchesAnyListedTypeName(AllowedTypes, false))); Finder->addMatcher( cxxOperatorCallExpr( hasOverloadedOperatorName("<<"), hasLHS(hasType(hasUnqualifiedDesugaredType( recordType(hasDeclaration(cxxRecordDecl( anyOf(BasicOstream, isDerivedFrom(BasicOstream)))))))), - hasRHS(hasType(hasUnqualifiedDesugaredType(isNumericChar())))) + hasRHS(expr(hasType(hasUnqualifiedDesugaredType(isNumericChar())), + unless(ignoringParenImpCasts( + anyOf(IsDeclRefExprFromAllowedTypes, + IsExplicitCastExprFromAllowedTypes)))))) .bind("x"), this); } diff --git a/clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.h b/clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.h index 61ea623d139ea..0759e3d1eb460 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.h @@ -30,6 +30,7 @@ class UnintendedCharOstreamOutputCheck : public ClangTidyCheck { } private: + const std::vector AllowedTypes; const std::optional CastTypeName; }; diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unintended-char-ostream-output.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unintended-char-ostream-output.rst index 95d02b3e2ddda..29254c4321f68 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unintended-char-ostream-output.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unintended-char-ostream-output.rst @@ -42,6 +42,16 @@ Or cast to char to explicitly indicate that output should be a character. Options ------- +.. option:: AllowedTypes + + A semicolon-separated list of type names that will be treated like the ``char`` + type: the check will not report variables declared with with these types or + explicit cast expressions to these types. Note that this distinguishes type + aliases from the original type, so specifying e.g. ``unsigned char`` here + will not suppress reports about ``uint8_t`` even if it is defined as a + ``typedef`` alias for ``unsigned char``. + Default is `unsigned char;signed char`. + .. option:: CastTypeName When `CastTypeName` is specified, the fix-it will use `CastTypeName` as the diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output-allowed-types.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output-allowed-types.cpp new file mode 100644 index 0000000000000..11dc207dfb0c3 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output-allowed-types.cpp @@ -0,0 +1,41 @@ +// RUN: %check_clang_tidy %s bugprone-unintended-char-ostream-output %t -- \ +// RUN: -config="{CheckOptions: \ +// RUN: {bugprone-unintended-char-ostream-output.AllowedTypes: \"\"}}" + +namespace std { + +template class basic_ostream { +public: + basic_ostream &operator<<(int); + basic_ostream &operator<<(unsigned int); +}; + +template +basic_ostream &operator<<(basic_ostream &, CharT); +template +basic_ostream &operator<<(basic_ostream &, char); +template +basic_ostream &operator<<(basic_ostream &, char); +template +basic_ostream &operator<<(basic_ostream &, + signed char); +template +basic_ostream &operator<<(basic_ostream &, + unsigned char); + +using ostream = basic_ostream; + +} // namespace std + +void origin_ostream(std::ostream &os) { + unsigned char unsigned_value = 9; + os << unsigned_value; + // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer + + signed char signed_value = 9; + os << signed_value; + // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'signed char' passed to 'operator<<' outputs as character instead of integer + + char char_value = 9; + os << char_value; +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output-cast-type.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output-cast-type.cpp index 72020d90e0369..f3c72dac654ad 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output-cast-type.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output-cast-type.cpp @@ -27,17 +27,18 @@ using ostream = basic_ostream; } // namespace std -class A : public std::ostream {}; +using uint8_t = unsigned char; +using int8_t = signed char; void origin_ostream(std::ostream &os) { - unsigned char unsigned_value = 9; + uint8_t unsigned_value = 9; os << unsigned_value; - // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer + // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'uint8_t' (aka 'unsigned char') passed to 'operator<<' outputs as character instead of integer // CHECK-FIXES: os << static_cast(unsigned_value); - signed char signed_value = 9; + int8_t signed_value = 9; os << signed_value; - // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'signed char' passed to 'operator<<' outputs as character instead of integer + // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'int8_t' (aka 'signed char') passed to 'operator<<' outputs as character instead of integer // CHECK-FIXES: os << static_cast(signed_value); char char_value = 9; diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output.cpp index 573c429bf049f..b458e55b7abc4 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output.cpp @@ -27,41 +27,56 @@ using ostream = basic_ostream; class A : public std::ostream {}; +using uint8_t = unsigned char; +using int8_t = signed char; + void origin_ostream(std::ostream &os) { - unsigned char unsigned_value = 9; + uint8_t unsigned_value = 9; os << unsigned_value; - // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer + // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'uint8_t' (aka 'unsigned char') passed to 'operator<<' outputs as character instead of integer // CHECK-FIXES: os << static_cast(unsigned_value); - signed char signed_value = 9; + int8_t signed_value = 9; os << signed_value; - // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'signed char' passed to 'operator<<' outputs as character instead of integer + // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'int8_t' (aka 'signed char') passed to 'operator<<' outputs as character instead of integer // CHECK-FIXES: os << static_cast(signed_value); char char_value = 9; os << char_value; + unsigned char unsigned_char_value = 9; + os << unsigned_char_value; + signed char signed_char_value = 9; + os << signed_char_value; +} + +void explicit_cast_to_char_type(std::ostream &os) { + enum V : uint8_t {}; + V e{}; + os << static_cast(e); + os << (unsigned char)(e); + os << (static_cast(e)); } void based_on_ostream(A &os) { - unsigned char unsigned_value = 9; + uint8_t unsigned_value = 9; os << unsigned_value; - // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer + // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'uint8_t' (aka 'unsigned char') passed to 'operator<<' outputs as character instead of integer // CHECK-FIXES: os << static_cast(unsigned_value); - signed char signed_value = 9; + int8_t signed_value = 9; os << signed_value; - // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'signed char' passed to 'operator<<' outputs as character instead of integer + // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'int8_t' (aka 'signed char') passed to 'operator<<' outputs as character instead of integer // CHECK-FIXES: os << static_cast(signed_value); char char_value = 9; os << char_value; } -void other_ostream_template_parameters(std::basic_ostream &os) { - unsigned char unsigned_value = 9; +void other_ostream_template_parameters(std::basic_ostream &os) { + uint8_t unsigned_value = 9; os << unsigned_value; - signed char signed_value = 9; + int8_t signed_value = 9; os << signed_value; char char_value = 9; @@ -70,23 +85,22 @@ void other_ostream_template_parameters(std::basic_ostream &os) { template class B : public std::ostream {}; void template_based_on_ostream(B &os) { - unsigned char unsigned_value = 9; + uint8_t unsigned_value = 9; os << unsigned_value; - // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer + // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'uint8_t' (aka 'unsigned char') passed to 'operator<<' outputs as character instead of integer // CHECK-FIXES: os << static_cast(unsigned_value); } template void template_fn_1(T &os) { - unsigned char unsigned_value = 9; + uint8_t unsigned_value = 9; os << unsigned_value; - // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer + // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'uint8_t' (aka 'unsigned char') passed to 'operator<<' outputs as character instead of integer // CHECK-FIXES: os << static_cast(unsigned_value); } template void template_fn_2(std::ostream &os) { T unsigned_value = 9; os << unsigned_value; - // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer - // CHECK-FIXES: os << static_cast(unsigned_value); + // It should be detected as well. But we cannot get the sugared type name for SubstTemplateTypeParmType. } template void template_fn_3(std::ostream &os) { T unsigned_value = 9; @@ -95,26 +109,10 @@ template void template_fn_3(std::ostream &os) { void call_template_fn() { A a{}; template_fn_1(a); - template_fn_2(a); + template_fn_2(a); template_fn_3(a); } -using U8 = unsigned char; -void alias_unsigned_char(std::ostream &os) { - U8 v = 9; - os << v; - // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'U8' (aka 'unsigned char') passed to 'operator<<' outputs as character instead of integer - // CHECK-FIXES: os << static_cast(v); -} - -using I8 = signed char; -void alias_signed_char(std::ostream &os) { - I8 v = 9; - os << v; - // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'I8' (aka 'signed char') passed to 'operator<<' outputs as character instead of integer - // CHECK-FIXES: os << static_cast(v); -} - using C8 = char; void alias_char(std::ostream &os) { C8 v = 9; @@ -124,8 +122,8 @@ void alias_char(std::ostream &os) { #define MACRO_VARIANT_NAME a void macro_variant_name(std::ostream &os) { - unsigned char MACRO_VARIANT_NAME = 9; + uint8_t MACRO_VARIANT_NAME = 9; os << MACRO_VARIANT_NAME; - // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer + // CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'uint8_t' (aka 'unsigned char') passed to 'operator<<' outputs as character instead of integer // CHECK-FIXES: os << static_cast(MACRO_VARIANT_NAME); }