Author: Congcong Cai Date: 2025-04-13T12:09:50+08:00 New Revision: 06814834a63139ff27efe3bdbc6dc15d46666b39
URL: https://github.com/llvm/llvm-project/commit/06814834a63139ff27efe3bdbc6dc15d46666b39 DIFF: https://github.com/llvm/llvm-project/commit/06814834a63139ff27efe3bdbc6dc15d46666b39.diff LOG: [clang-tidy] treat unsigned char and signed char as char type by default in bugprone-unintended-char-ostream-output (#134870) Add `AllowedTypes` options to support custom defined char like type. treat `unsigned char` and `signed char` as char like type by default. The allowed types only effect when the var decl or explicit cast to this non-canonical type names. Fixed: #133425 Added: clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output-allowed-types.cpp Modified: clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.cpp clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.h clang-tools-extra/docs/clang-tidy/checks/bugprone/unintended-char-ostream-output.rst clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output-cast-type.cpp clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output.cpp Removed: ################################################################################ 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<StringRef> AllowedTypes; const std::optional<StringRef> 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 _CharT, class _Traits = void> class basic_ostream { +public: + basic_ostream &operator<<(int); + basic_ostream &operator<<(unsigned int); +}; + +template <class CharT, class Traits> +basic_ostream<CharT, Traits> &operator<<(basic_ostream<CharT, Traits> &, CharT); +template <class CharT, class Traits> +basic_ostream<CharT, Traits> &operator<<(basic_ostream<CharT, Traits> &, char); +template <class _Traits> +basic_ostream<char, _Traits> &operator<<(basic_ostream<char, _Traits> &, char); +template <class _Traits> +basic_ostream<char, _Traits> &operator<<(basic_ostream<char, _Traits> &, + signed char); +template <class _Traits> +basic_ostream<char, _Traits> &operator<<(basic_ostream<char, _Traits> &, + unsigned char); + +using ostream = basic_ostream<char>; + +} // 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<char>; } // 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 char>(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<unsigned char>(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<char>; 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 int>(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<int>(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<unsigned char>(e); + os << (unsigned char)(e); + os << (static_cast<unsigned char>(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 int>(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<int>(signed_value); char char_value = 9; os << char_value; } -void other_ostream_template_parameters(std::basic_ostream<unsigned char> &os) { - unsigned char unsigned_value = 9; +void other_ostream_template_parameters(std::basic_ostream<uint8_t> &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<unsigned char> &os) { template <class T> class B : public std::ostream {}; void template_based_on_ostream(B<int> &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 int>(unsigned_value); } template<class T> 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 int>(unsigned_value); } template<class T> 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 int>(unsigned_value); + // It should be detected as well. But we cannot get the sugared type name for SubstTemplateTypeParmType. } template<class T> void template_fn_3(std::ostream &os) { T unsigned_value = 9; @@ -95,26 +109,10 @@ template<class T> void template_fn_3(std::ostream &os) { void call_template_fn() { A a{}; template_fn_1(a); - template_fn_2<unsigned char>(a); + template_fn_2<uint8_t>(a); template_fn_3<char>(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<unsigned int>(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<int>(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<unsigned int>(MACRO_VARIANT_NAME); } _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits