Skip to content

[clang-tidy] treat unsigned char and signed char as char type by default in bugprone-unintended-char-ostream-output #134870

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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());
}
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class UnintendedCharOstreamOutputCheck : public ClangTidyCheck {
}

private:
const std::vector<StringRef> AllowedTypes;
const std::optional<StringRef> CastTypeName;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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);
}
Loading