-
Notifications
You must be signed in to change notification settings - Fork 15.4k
[clang-tidy] Add new check bugprone-unintended-char-ostream-output #127720
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
Changes from 2 commits
b69bb46
22b8bd8
9033197
824f287
7f6405e
00df49d
bc43f1f
82cb4d0
553d6cd
2ec0afd
4d3916e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| //===--- UnintendedCharOstreamOutputCheck.cpp - clang-tidy ----------------===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "UnintendedCharOstreamOutputCheck.h" | ||
| #include "clang/AST/Type.h" | ||
| #include "clang/ASTMatchers/ASTMatchFinder.h" | ||
| #include "clang/ASTMatchers/ASTMatchers.h" | ||
|
|
||
| using namespace clang::ast_matchers; | ||
|
|
||
| namespace clang::tidy::bugprone { | ||
|
|
||
| namespace { | ||
|
|
||
| // check if the type is unsigned char or signed char | ||
| AST_MATCHER(Type, isNumericChar) { | ||
| const auto *BT = dyn_cast<BuiltinType>(&Node); | ||
| if (BT == nullptr) | ||
| return false; | ||
| const BuiltinType::Kind K = BT->getKind(); | ||
| return K == BuiltinType::UChar || K == BuiltinType::SChar; | ||
| } | ||
|
|
||
| // check if the type is char | ||
| AST_MATCHER(Type, isChar) { | ||
| const auto *BT = dyn_cast<BuiltinType>(&Node); | ||
| if (BT == nullptr) | ||
| return false; | ||
| const BuiltinType::Kind K = BT->getKind(); | ||
| return K == BuiltinType::Char_U || K == BuiltinType::Char_S; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| void UnintendedCharOstreamOutputCheck::registerMatchers(MatchFinder *Finder) { | ||
| auto BasicOstream = | ||
| cxxRecordDecl(hasName("::std::basic_ostream"), | ||
| // only basic_ostream<char, Traits> has overload operator<< | ||
| // with char / unsigned char / signed char | ||
| classTemplateSpecializationDecl( | ||
| hasTemplateArgument(0, refersToType(isChar())))); | ||
| Finder->addMatcher( | ||
| cxxOperatorCallExpr( | ||
| hasOverloadedOperatorName("<<"), | ||
| hasLHS(hasType(hasUnqualifiedDesugaredType( | ||
| recordType(hasDeclaration(cxxRecordDecl( | ||
| anyOf(BasicOstream, isDerivedFrom(BasicOstream)))))))), | ||
| hasRHS(hasType(hasUnqualifiedDesugaredType(isNumericChar())))) | ||
| .bind("x"), | ||
| this); | ||
| } | ||
|
|
||
| void UnintendedCharOstreamOutputCheck::check( | ||
| const MatchFinder::MatchResult &Result) { | ||
| const auto *Call = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("x"); | ||
| const Expr *Value = Call->getArg(1); | ||
| diag(Call->getOperatorLoc(), | ||
| "(%0 passed to 'operator<<' outputs as character instead of integer. " | ||
| "cast to 'unsigned' to print numeric value or cast to 'char' to print " | ||
| "as character)") | ||
| << Value->getType() << Value->getSourceRange(); | ||
|
||
| } | ||
|
|
||
| } // namespace clang::tidy::bugprone | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| //===--- UnintendedCharOstreamOutputCheck.h - clang-tidy --------*- C++ -*-===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNINTENDEDCHAROSTREAMOUTPUTCHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNINTENDEDCHAROSTREAMOUTPUTCHECK_H | ||
|
|
||
| #include "../ClangTidyCheck.h" | ||
|
|
||
| namespace clang::tidy::bugprone { | ||
|
|
||
| /// Finds unintended character output from `unsigned char` and `signed char` to | ||
| /// an ostream. | ||
| /// | ||
| /// For the user-facing documentation see: | ||
| /// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/unintended-char-ostream-output.html | ||
| class UnintendedCharOstreamOutputCheck : public ClangTidyCheck { | ||
| public: | ||
| UnintendedCharOstreamOutputCheck(StringRef Name, ClangTidyContext *Context) | ||
| : ClangTidyCheck(Name, Context) {} | ||
| void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
| void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
| bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { | ||
| return LangOpts.CPlusPlus; | ||
| } | ||
| }; | ||
|
|
||
| } // namespace clang::tidy::bugprone | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNINTENDEDCHAROSTREAMOUTPUTCHECK_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| .. title:: clang-tidy - bugprone-unintended-char-ostream-output | ||
|
|
||
| bugprone-unintended-char-ostream-output | ||
| ======================================= | ||
|
|
||
| Finds unintended character output from ``unsigned char`` and ``signed char`` to an | ||
| ``ostream``. | ||
|
|
||
| Normally, when ``unsigned char (uint8_t)`` or ``signed char (int8_t)`` is used, it | ||
| is more likely a number than a character. However, when it is passed directly to | ||
| ``std::ostream``'s ``operator<<``, resulting in character-based output instead | ||
| of numeric value. This often contradicts the developer's intent to print | ||
HerrCai0907 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| integer values. | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| uint8_t v = 9; | ||
| std::cout << v; // output '\t' instead of '9' | ||
HerrCai0907 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| It could be fixed as | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| std::cout << (uint32_t)v; | ||
HerrCai0907 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Or cast to char to explicitly indicate the intent | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| std::cout << (char)v; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| // RUN: %check_clang_tidy %s bugprone-unintended-char-ostream-output %t | ||
|
|
||
| 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 | ||
|
|
||
| class A : public std::ostream {}; | ||
|
|
||
| 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; | ||
| } | ||
|
|
||
| void based_on_ostream(A &os) { | ||
| unsigned char unsigned_value = 9; | ||
| os << unsigned_value; | ||
| // CHECK-MESSAGES: [[@LINE-1]]:6: warning: ('unsigned char' passed to | ||
HerrCai0907 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // '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; | ||
HerrCai0907 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| void based_on_ostream(std::basic_ostream<unsigned char> &os) { | ||
HerrCai0907 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| unsigned char unsigned_value = 9; | ||
| os << unsigned_value; | ||
|
|
||
| signed char signed_value = 9; | ||
| os << signed_value; | ||
|
|
||
| char char_value = 9; | ||
| os << char_value; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.