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

Reply via email to