Skip to content

Commit 0681483

Browse files
authored
[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
1 parent 302bc41 commit 0681483

File tree

6 files changed

+108
-44
lines changed

6 files changed

+108
-44
lines changed

clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.cpp

+16-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "UnintendedCharOstreamOutputCheck.h"
10+
#include "../utils/Matchers.h"
11+
#include "../utils/OptionsUtils.h"
1012
#include "clang/AST/Type.h"
1113
#include "clang/ASTMatchers/ASTMatchFinder.h"
1214
#include "clang/ASTMatchers/ASTMatchers.h"
@@ -35,10 +37,14 @@ AST_MATCHER(Type, isChar) {
3537

3638
UnintendedCharOstreamOutputCheck::UnintendedCharOstreamOutputCheck(
3739
StringRef Name, ClangTidyContext *Context)
38-
: ClangTidyCheck(Name, Context), CastTypeName(Options.get("CastTypeName")) {
39-
}
40+
: ClangTidyCheck(Name, Context),
41+
AllowedTypes(utils::options::parseStringList(
42+
Options.get("AllowedTypes", "unsigned char;signed char"))),
43+
CastTypeName(Options.get("CastTypeName")) {}
4044
void UnintendedCharOstreamOutputCheck::storeOptions(
4145
ClangTidyOptions::OptionMap &Opts) {
46+
Options.store(Opts, "AllowedTypes",
47+
utils::options::serializeStringList(AllowedTypes));
4248
if (CastTypeName.has_value())
4349
Options.store(Opts, "CastTypeName", CastTypeName.value());
4450
}
@@ -50,13 +56,20 @@ void UnintendedCharOstreamOutputCheck::registerMatchers(MatchFinder *Finder) {
5056
// with char / unsigned char / signed char
5157
classTemplateSpecializationDecl(
5258
hasTemplateArgument(0, refersToType(isChar()))));
59+
auto IsDeclRefExprFromAllowedTypes = declRefExpr(to(varDecl(
60+
hasType(matchers::matchesAnyListedTypeName(AllowedTypes, false)))));
61+
auto IsExplicitCastExprFromAllowedTypes = explicitCastExpr(hasDestinationType(
62+
matchers::matchesAnyListedTypeName(AllowedTypes, false)));
5363
Finder->addMatcher(
5464
cxxOperatorCallExpr(
5565
hasOverloadedOperatorName("<<"),
5666
hasLHS(hasType(hasUnqualifiedDesugaredType(
5767
recordType(hasDeclaration(cxxRecordDecl(
5868
anyOf(BasicOstream, isDerivedFrom(BasicOstream)))))))),
59-
hasRHS(hasType(hasUnqualifiedDesugaredType(isNumericChar()))))
69+
hasRHS(expr(hasType(hasUnqualifiedDesugaredType(isNumericChar())),
70+
unless(ignoringParenImpCasts(
71+
anyOf(IsDeclRefExprFromAllowedTypes,
72+
IsExplicitCastExprFromAllowedTypes))))))
6073
.bind("x"),
6174
this);
6275
}

clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.h

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class UnintendedCharOstreamOutputCheck : public ClangTidyCheck {
3030
}
3131

3232
private:
33+
const std::vector<StringRef> AllowedTypes;
3334
const std::optional<StringRef> CastTypeName;
3435
};
3536

clang-tools-extra/docs/clang-tidy/checks/bugprone/unintended-char-ostream-output.rst

+10
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ Or cast to char to explicitly indicate that output should be a character.
4242
Options
4343
-------
4444

45+
.. option:: AllowedTypes
46+
47+
A semicolon-separated list of type names that will be treated like the ``char``
48+
type: the check will not report variables declared with with these types or
49+
explicit cast expressions to these types. Note that this distinguishes type
50+
aliases from the original type, so specifying e.g. ``unsigned char`` here
51+
will not suppress reports about ``uint8_t`` even if it is defined as a
52+
``typedef`` alias for ``unsigned char``.
53+
Default is `unsigned char;signed char`.
54+
4555
.. option:: CastTypeName
4656

4757
When `CastTypeName` is specified, the fix-it will use `CastTypeName` as the
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// RUN: %check_clang_tidy %s bugprone-unintended-char-ostream-output %t -- \
2+
// RUN: -config="{CheckOptions: \
3+
// RUN: {bugprone-unintended-char-ostream-output.AllowedTypes: \"\"}}"
4+
5+
namespace std {
6+
7+
template <class _CharT, class _Traits = void> class basic_ostream {
8+
public:
9+
basic_ostream &operator<<(int);
10+
basic_ostream &operator<<(unsigned int);
11+
};
12+
13+
template <class CharT, class Traits>
14+
basic_ostream<CharT, Traits> &operator<<(basic_ostream<CharT, Traits> &, CharT);
15+
template <class CharT, class Traits>
16+
basic_ostream<CharT, Traits> &operator<<(basic_ostream<CharT, Traits> &, char);
17+
template <class _Traits>
18+
basic_ostream<char, _Traits> &operator<<(basic_ostream<char, _Traits> &, char);
19+
template <class _Traits>
20+
basic_ostream<char, _Traits> &operator<<(basic_ostream<char, _Traits> &,
21+
signed char);
22+
template <class _Traits>
23+
basic_ostream<char, _Traits> &operator<<(basic_ostream<char, _Traits> &,
24+
unsigned char);
25+
26+
using ostream = basic_ostream<char>;
27+
28+
} // namespace std
29+
30+
void origin_ostream(std::ostream &os) {
31+
unsigned char unsigned_value = 9;
32+
os << unsigned_value;
33+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer
34+
35+
signed char signed_value = 9;
36+
os << signed_value;
37+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'signed char' passed to 'operator<<' outputs as character instead of integer
38+
39+
char char_value = 9;
40+
os << char_value;
41+
}

clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output-cast-type.cpp

+6-5
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,18 @@ using ostream = basic_ostream<char>;
2727

2828
} // namespace std
2929

30-
class A : public std::ostream {};
30+
using uint8_t = unsigned char;
31+
using int8_t = signed char;
3132

3233
void origin_ostream(std::ostream &os) {
33-
unsigned char unsigned_value = 9;
34+
uint8_t unsigned_value = 9;
3435
os << unsigned_value;
35-
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer
36+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'uint8_t' (aka 'unsigned char') passed to 'operator<<' outputs as character instead of integer
3637
// CHECK-FIXES: os << static_cast<unsigned char>(unsigned_value);
3738

38-
signed char signed_value = 9;
39+
int8_t signed_value = 9;
3940
os << signed_value;
40-
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'signed char' passed to 'operator<<' outputs as character instead of integer
41+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'int8_t' (aka 'signed char') passed to 'operator<<' outputs as character instead of integer
4142
// CHECK-FIXES: os << static_cast<unsigned char>(signed_value);
4243

4344
char char_value = 9;

clang-tools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output.cpp

+34-36
Original file line numberDiff line numberDiff line change
@@ -27,41 +27,56 @@ using ostream = basic_ostream<char>;
2727

2828
class A : public std::ostream {};
2929

30+
using uint8_t = unsigned char;
31+
using int8_t = signed char;
32+
3033
void origin_ostream(std::ostream &os) {
31-
unsigned char unsigned_value = 9;
34+
uint8_t unsigned_value = 9;
3235
os << unsigned_value;
33-
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer
36+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'uint8_t' (aka 'unsigned char') passed to 'operator<<' outputs as character instead of integer
3437
// CHECK-FIXES: os << static_cast<unsigned int>(unsigned_value);
3538

36-
signed char signed_value = 9;
39+
int8_t signed_value = 9;
3740
os << signed_value;
38-
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'signed char' passed to 'operator<<' outputs as character instead of integer
41+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'int8_t' (aka 'signed char') passed to 'operator<<' outputs as character instead of integer
3942
// CHECK-FIXES: os << static_cast<int>(signed_value);
4043

4144
char char_value = 9;
4245
os << char_value;
46+
unsigned char unsigned_char_value = 9;
47+
os << unsigned_char_value;
48+
signed char signed_char_value = 9;
49+
os << signed_char_value;
50+
}
51+
52+
void explicit_cast_to_char_type(std::ostream &os) {
53+
enum V : uint8_t {};
54+
V e{};
55+
os << static_cast<unsigned char>(e);
56+
os << (unsigned char)(e);
57+
os << (static_cast<unsigned char>(e));
4358
}
4459

4560
void based_on_ostream(A &os) {
46-
unsigned char unsigned_value = 9;
61+
uint8_t unsigned_value = 9;
4762
os << unsigned_value;
48-
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer
63+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'uint8_t' (aka 'unsigned char') passed to 'operator<<' outputs as character instead of integer
4964
// CHECK-FIXES: os << static_cast<unsigned int>(unsigned_value);
5065

51-
signed char signed_value = 9;
66+
int8_t signed_value = 9;
5267
os << signed_value;
53-
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'signed char' passed to 'operator<<' outputs as character instead of integer
68+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'int8_t' (aka 'signed char') passed to 'operator<<' outputs as character instead of integer
5469
// CHECK-FIXES: os << static_cast<int>(signed_value);
5570

5671
char char_value = 9;
5772
os << char_value;
5873
}
5974

60-
void other_ostream_template_parameters(std::basic_ostream<unsigned char> &os) {
61-
unsigned char unsigned_value = 9;
75+
void other_ostream_template_parameters(std::basic_ostream<uint8_t> &os) {
76+
uint8_t unsigned_value = 9;
6277
os << unsigned_value;
6378

64-
signed char signed_value = 9;
79+
int8_t signed_value = 9;
6580
os << signed_value;
6681

6782
char char_value = 9;
@@ -70,23 +85,22 @@ void other_ostream_template_parameters(std::basic_ostream<unsigned char> &os) {
7085

7186
template <class T> class B : public std::ostream {};
7287
void template_based_on_ostream(B<int> &os) {
73-
unsigned char unsigned_value = 9;
88+
uint8_t unsigned_value = 9;
7489
os << unsigned_value;
75-
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer
90+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'uint8_t' (aka 'unsigned char') passed to 'operator<<' outputs as character instead of integer
7691
// CHECK-FIXES: os << static_cast<unsigned int>(unsigned_value);
7792
}
7893

7994
template<class T> void template_fn_1(T &os) {
80-
unsigned char unsigned_value = 9;
95+
uint8_t unsigned_value = 9;
8196
os << unsigned_value;
82-
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer
97+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'uint8_t' (aka 'unsigned char') passed to 'operator<<' outputs as character instead of integer
8398
// CHECK-FIXES: os << static_cast<unsigned int>(unsigned_value);
8499
}
85100
template<class T> void template_fn_2(std::ostream &os) {
86101
T unsigned_value = 9;
87102
os << unsigned_value;
88-
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer
89-
// CHECK-FIXES: os << static_cast<unsigned int>(unsigned_value);
103+
// It should be detected as well. But we cannot get the sugared type name for SubstTemplateTypeParmType.
90104
}
91105
template<class T> void template_fn_3(std::ostream &os) {
92106
T unsigned_value = 9;
@@ -95,26 +109,10 @@ template<class T> void template_fn_3(std::ostream &os) {
95109
void call_template_fn() {
96110
A a{};
97111
template_fn_1(a);
98-
template_fn_2<unsigned char>(a);
112+
template_fn_2<uint8_t>(a);
99113
template_fn_3<char>(a);
100114
}
101115

102-
using U8 = unsigned char;
103-
void alias_unsigned_char(std::ostream &os) {
104-
U8 v = 9;
105-
os << v;
106-
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'U8' (aka 'unsigned char') passed to 'operator<<' outputs as character instead of integer
107-
// CHECK-FIXES: os << static_cast<unsigned int>(v);
108-
}
109-
110-
using I8 = signed char;
111-
void alias_signed_char(std::ostream &os) {
112-
I8 v = 9;
113-
os << v;
114-
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'I8' (aka 'signed char') passed to 'operator<<' outputs as character instead of integer
115-
// CHECK-FIXES: os << static_cast<int>(v);
116-
}
117-
118116
using C8 = char;
119117
void alias_char(std::ostream &os) {
120118
C8 v = 9;
@@ -124,8 +122,8 @@ void alias_char(std::ostream &os) {
124122

125123
#define MACRO_VARIANT_NAME a
126124
void macro_variant_name(std::ostream &os) {
127-
unsigned char MACRO_VARIANT_NAME = 9;
125+
uint8_t MACRO_VARIANT_NAME = 9;
128126
os << MACRO_VARIANT_NAME;
129-
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'unsigned char' passed to 'operator<<' outputs as character instead of integer
127+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: 'uint8_t' (aka 'unsigned char') passed to 'operator<<' outputs as character instead of integer
130128
// CHECK-FIXES: os << static_cast<unsigned int>(MACRO_VARIANT_NAME);
131129
}

0 commit comments

Comments
 (0)