mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-14 17:06:38 +00:00
[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
This commit is contained in:
parent
302bc41410
commit
06814834a6
@ -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);
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<StringRef> AllowedTypes;
|
||||
const std::optional<StringRef> CastTypeName;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user