-
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
Merged
HerrCai0907
merged 11 commits into
llvm:main
from
HerrCai0907:bugprone/unintended-char-ostream-output
Feb 27, 2025
Merged
Changes from 10 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
b69bb46
[clang-tidy]add new check bugprone-unintended-char-ostream-output
HerrCai0907 22b8bd8
fix review
HerrCai0907 9033197
fix review
HerrCai0907 824f287
Update clang-tools-extra/docs/clang-tidy/checks/bugprone/unintended-c…
HerrCai0907 7f6405e
fix review
HerrCai0907 00df49d
template test
HerrCai0907 bc43f1f
add more test case
HerrCai0907 82cb4d0
add option
HerrCai0907 553d6cd
rework doc
HerrCai0907 2ec0afd
Merge remote-tracking branch 'origin/main' into bugprone/unintended-c…
HerrCai0907 4d3916e
add doc
HerrCai0907 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| //===--- 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" | ||
| #include "clang/Basic/Diagnostic.h" | ||
| #include "clang/Tooling/FixIt.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) { | ||
| return Node.isSpecificBuiltinType(BuiltinType::SChar) || | ||
| Node.isSpecificBuiltinType(BuiltinType::UChar); | ||
| } | ||
|
|
||
| // check if the type is char | ||
| AST_MATCHER(Type, isChar) { | ||
| return Node.isSpecificBuiltinType(BuiltinType::Char_S) || | ||
| Node.isSpecificBuiltinType(BuiltinType::Char_U); | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| UnintendedCharOstreamOutputCheck::UnintendedCharOstreamOutputCheck( | ||
| StringRef Name, ClangTidyContext *Context) | ||
| : ClangTidyCheck(Name, Context), CastTypeName(Options.get("CastTypeName")) { | ||
| } | ||
| void UnintendedCharOstreamOutputCheck::storeOptions( | ||
| ClangTidyOptions::OptionMap &Opts) { | ||
| if (CastTypeName.has_value()) | ||
| Options.store(Opts, "CastTypeName", CastTypeName.value()); | ||
| } | ||
|
|
||
| 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); | ||
| const SourceRange SourceRange = Value->getSourceRange(); | ||
|
|
||
| DiagnosticBuilder Builder = | ||
| diag(Call->getOperatorLoc(), | ||
| "%0 passed to 'operator<<' outputs as character instead of integer. " | ||
| "cast to 'unsigned int' to print numeric value or cast to 'char' to " | ||
| "print as character") | ||
| << Value->getType() << SourceRange; | ||
|
|
||
| QualType T = Value->getType(); | ||
| const Type *UnqualifiedDesugaredType = T->getUnqualifiedDesugaredType(); | ||
|
|
||
| llvm::StringRef CastType = CastTypeName.value_or( | ||
| UnqualifiedDesugaredType->isSpecificBuiltinType(BuiltinType::SChar) | ||
| ? "int" | ||
| : "unsigned int"); | ||
|
|
||
| Builder << FixItHint::CreateReplacement( | ||
| SourceRange, ("static_cast<" + CastType + ">(" + | ||
| tooling::fixit::getText(*Value, *Result.Context) + ")") | ||
| .str()); | ||
| } | ||
|
|
||
| } // namespace clang::tidy::bugprone |
38 changes: 38 additions & 0 deletions
38
clang-tools-extra/clang-tidy/bugprone/UnintendedCharOstreamOutputCheck.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| //===--- 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" | ||
| #include <optional> | ||
|
|
||
| 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); | ||
| void storeOptions(ClangTidyOptions::OptionMap &Opts) override; | ||
| 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; | ||
| } | ||
|
|
||
| private: | ||
| const std::optional<StringRef> CastTypeName; | ||
| }; | ||
|
|
||
| } // namespace clang::tidy::bugprone | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNINTENDEDCHAROSTREAMOUTPUTCHECK_H |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
...-tools-extra/docs/clang-tidy/checks/bugprone/unintended-char-ostream-output.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| .. 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<<``, the result is the character output instead | ||
| of the numeric value. This often contradicts the developer's intent to print | ||
| integer values. | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| uint8_t v = 65; | ||
| std::cout << v; // output 'A' instead of '65' | ||
|
|
||
| The check will suggest casting the value to an appropriate type to indicate the | ||
| intent, by default, it will cast to ``unsigned int`` for ``unsigned char`` and | ||
| ``int`` for ``signed char``. | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| std::cout << static_cast<unsigned int>(v); // when v is unsigned char | ||
| std::cout << static_cast<int>(v); // when v is signed char | ||
|
|
||
| To avoid lengthy cast statements, add prefix ``+`` to the variable can also | ||
| suppress warnings. | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| std::cout << +v; | ||
|
|
||
| Or cast to char to explicitly indicate that output should be a character. | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| std::cout << static_cast<char>(v); | ||
|
|
||
| .. option:: CastTypeName | ||
|
|
||
| When `CastTypeName` is specified, the fix-it will use `CastTypeName` as the | ||
| cast target type. Otherwise, fix-it will automatically infer the type. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
...ools-extra/test/clang-tidy/checkers/bugprone/unintended-char-ostream-output-cast-type.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| // RUN: %check_clang_tidy %s bugprone-unintended-char-ostream-output %t -- \ | ||
| // RUN: -config="{CheckOptions: \ | ||
| // RUN: {bugprone-unintended-char-ostream-output.CastTypeName: "uint8_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 | ||
| // CHECK-FIXES: os << static_cast<uint8_t>(unsigned_value); | ||
|
|
||
| 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 | ||
| // CHECK-FIXES: os << static_cast<uint8_t>(signed_value); | ||
|
|
||
| char char_value = 9; | ||
| os << char_value; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.