[clang-tools-extra] [clang-tidy] treat unsigned char and signed char as char type by default in bugprone-unintended-char-ostream-output (PR #134870)
Congcong Cai via cfe-commits
cfe-commits at lists.llvm.org
Sat Apr 12 21:07:22 PDT 2025
https://github.com/HerrCai0907 updated https://github.com/llvm/llvm-project/pull/134870
>From badfb83ff201f021363e9634b4296e040251d408 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Tue, 8 Apr 2025 15:27:54 +0000
Subject: [PATCH 1/2] [clang-tidy] treat unsigned char and signed char as char
type by default in bugprone-unintended-char-ostream-output
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.
---
.../UnintendedCharOstreamOutputCheck.cpp | 19 ++++-
.../UnintendedCharOstreamOutputCheck.h | 1 +
.../unintended-char-ostream-output.rst | 8 +++
...nded-char-ostream-output-allowed-types.cpp | 41 +++++++++++
...intended-char-ostream-output-cast-type.cpp | 11 +--
.../unintended-char-ostream-output.cpp | 70 +++++++++----------
6 files changed, 106 insertions(+), 44 deletions(-)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output-allowed-types.cpp
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..9ad08188d7fb2 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,14 @@ 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 as ``char``
+ type. It only contains the non canonical type names without the alias of type
+ names. Explicit casting to these types or use the variable defined with these
+ types will be ignored.
+ 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);
}
>From b9b424aac3796077b06326c7552c6356d0da76f5 Mon Sep 17 00:00:00 2001
From: Congcong Cai <congcongcai0907 at 163.com>
Date: Wed, 9 Apr 2025 21:43:52 +0800
Subject: [PATCH 2/2] Update unintended-char-ostream-output.rst
---
.../checks/bugprone/unintended-char-ostream-output.rst | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
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 9ad08188d7fb2..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
@@ -44,10 +44,12 @@ Options
.. option:: AllowedTypes
- A semicolon-separated list of type names that will be treated as ``char``
- type. It only contains the non canonical type names without the alias of type
- names. Explicit casting to these types or use the variable defined with these
- types will be ignored.
+ 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
More information about the cfe-commits
mailing list