Skip to content

Commit b69bb46

Browse files
committed
[clang-tidy]add new check bugprone-unintended-char-ostream-output
It wants to find unintended character output from and to an ostream. e.g. uint8_t v = 9; std::cout << v;
1 parent ed48398 commit b69bb46

File tree

8 files changed

+215
-0
lines changed

8 files changed

+215
-0
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
#include "UndelegatedConstructorCheck.h"
9191
#include "UnhandledExceptionAtNewCheck.h"
9292
#include "UnhandledSelfAssignmentCheck.h"
93+
#include "UnintendedCharOstreamOutputCheck.h"
9394
#include "UniquePtrArrayMismatchCheck.h"
9495
#include "UnsafeFunctionsCheck.h"
9596
#include "UnusedLocalNonTrivialVariableCheck.h"
@@ -147,6 +148,8 @@ class BugproneModule : public ClangTidyModule {
147148
"bugprone-incorrect-enable-if");
148149
CheckFactories.registerCheck<IncorrectEnableSharedFromThisCheck>(
149150
"bugprone-incorrect-enable-shared-from-this");
151+
CheckFactories.registerCheck<UnintendedCharOstreamOutputCheck>(
152+
"bugprone-unintended-char-ostream-output");
150153
CheckFactories.registerCheck<ReturnConstRefFromParameterCheck>(
151154
"bugprone-return-const-ref-from-parameter");
152155
CheckFactories.registerCheck<SwitchMissingDefaultCaseCheck>(

clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ add_clang_library(clangTidyBugproneModule STATIC
2929
InaccurateEraseCheck.cpp
3030
IncorrectEnableIfCheck.cpp
3131
IncorrectEnableSharedFromThisCheck.cpp
32+
UnintendedCharOstreamOutputCheck.cpp
3233
ReturnConstRefFromParameterCheck.cpp
3334
SuspiciousStringviewDataUsageCheck.cpp
3435
SwitchMissingDefaultCaseCheck.cpp
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//===--- UnintendedCharOstreamOutputCheck.cpp - clang-tidy
2+
//---------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#include "UnintendedCharOstreamOutputCheck.h"
11+
#include "clang/AST/Type.h"
12+
#include "clang/ASTMatchers/ASTMatchFinder.h"
13+
#include "clang/ASTMatchers/ASTMatchers.h"
14+
15+
using namespace clang::ast_matchers;
16+
17+
namespace clang::tidy::bugprone {
18+
19+
namespace {
20+
21+
// check if the type is unsigned char or signed char
22+
AST_MATCHER(Type, isNumericChar) {
23+
const auto *BT = dyn_cast<BuiltinType>(&Node);
24+
if (BT == nullptr)
25+
return false;
26+
const BuiltinType::Kind K = BT->getKind();
27+
return K == BuiltinType::UChar || K == BuiltinType::SChar;
28+
}
29+
30+
// check if the type is char
31+
AST_MATCHER(Type, isChar) {
32+
const auto *BT = dyn_cast<BuiltinType>(&Node);
33+
if (BT == nullptr)
34+
return false;
35+
const BuiltinType::Kind K = BT->getKind();
36+
return K == BuiltinType::Char_U || K == BuiltinType::Char_S;
37+
}
38+
39+
} // namespace
40+
41+
void UnintendedCharOstreamOutputCheck::registerMatchers(MatchFinder *Finder) {
42+
auto BasicOstream =
43+
cxxRecordDecl(hasName("::std::basic_ostream"),
44+
// only basic_ostream<char, Traits> has overload operator<<
45+
// with char / unsigned char / signed char
46+
classTemplateSpecializationDecl(
47+
hasTemplateArgument(0, refersToType(isChar()))));
48+
Finder->addMatcher(
49+
cxxOperatorCallExpr(
50+
hasOverloadedOperatorName("<<"),
51+
hasLHS(hasType(hasUnqualifiedDesugaredType(
52+
recordType(hasDeclaration(cxxRecordDecl(
53+
anyOf(BasicOstream, isDerivedFrom(BasicOstream)))))))),
54+
hasRHS(hasType(hasUnqualifiedDesugaredType(isNumericChar()))))
55+
.bind("x"),
56+
this);
57+
}
58+
59+
void UnintendedCharOstreamOutputCheck::check(
60+
const MatchFinder::MatchResult &Result) {
61+
const auto *Call = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("x");
62+
const Expr *Value = Call->getArg(1);
63+
diag(Call->getOperatorLoc(),
64+
"(%0 passed to 'operator<<' outputs as character instead of integer. "
65+
"cast to 'unsigned' to print numeric value or cast to 'char' to print "
66+
"as character)")
67+
<< Value->getType() << Value->getSourceRange();
68+
}
69+
70+
} // namespace clang::tidy::bugprone
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//===--- UnintendedCharOstreamOutputCheck.h - clang-tidy --------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNINTENDEDCHAROSTREAMOUTPUTCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNINTENDEDCHAROSTREAMOUTPUTCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::bugprone {
15+
16+
/// Finds unintended character output from `unsigned char` and `signed char` to
17+
/// an ostream.
18+
///
19+
/// For the user-facing documentation see:
20+
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/unintended-char-ostream-output.html
21+
class UnintendedCharOstreamOutputCheck : public ClangTidyCheck {
22+
public:
23+
UnintendedCharOstreamOutputCheck(StringRef Name, ClangTidyContext *Context)
24+
: ClangTidyCheck(Name, Context) {}
25+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
26+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
27+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
28+
return LangOpts.CPlusPlus;
29+
}
30+
};
31+
32+
} // namespace clang::tidy::bugprone
33+
34+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNINTENDEDCHAROSTREAMOUTPUTCHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ Improvements to clang-tidy
9191
New checks
9292
^^^^^^^^^^
9393

94+
- New :doc:`bugprone-unintended-char-ostream-output
95+
<clang-tidy/checks/bugprone/unintended-char-ostream-output>` check.
96+
97+
Finds unintended character output from `unsigned char` and `signed char` to an
98+
ostream.
99+
94100
New check aliases
95101
^^^^^^^^^^^^^^^^^
96102

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
.. title:: clang-tidy - bugprone-unintended-char-ostream-output
2+
3+
bugprone-unintended-char-ostream-output
4+
=======================================
5+
6+
Finds unintended character output from `unsigned char` and `signed char` to an
7+
``ostream``.
8+
9+
Normally, when ``unsigned char (uint8_t)`` or ``signed char (int8_t)`` is used, it
10+
is more likely a number than a character. However, when it is passed directly to
11+
``std::ostream``'s ``operator<<``, resulting in character-based output instead
12+
of numeric value. This often contradicts the developer's intent to print
13+
integer values.
14+
15+
.. code-block:: c++
16+
17+
uint8_t v = 9;
18+
std::cout << v; // output '\t' instead of '9'
19+
20+
It could be fixed as
21+
22+
.. code-block:: c++
23+
24+
std::cout << (uint32_t)v;
25+
26+
Or cast to char to explicitly indicate the intent
27+
28+
.. code-block:: c++
29+
30+
std::cout << (char)v;

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ Clang-Tidy Checks
158158
:doc:`bugprone-undelegated-constructor <bugprone/undelegated-constructor>`,
159159
:doc:`bugprone-unhandled-exception-at-new <bugprone/unhandled-exception-at-new>`,
160160
:doc:`bugprone-unhandled-self-assignment <bugprone/unhandled-self-assignment>`,
161+
:doc:`bugprone-unintended-char-ostream-output <bugprone/unintended-char-ostream-output>`,
161162
:doc:`bugprone-unique-ptr-array-mismatch <bugprone/unique-ptr-array-mismatch>`, "Yes"
162163
:doc:`bugprone-unsafe-functions <bugprone/unsafe-functions>`,
163164
:doc:`bugprone-unused-local-non-trivial-variable <bugprone/unused-local-non-trivial-variable>`,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// RUN: %check_clang_tidy %s bugprone-unintended-char-ostream-output %t
2+
3+
namespace std {
4+
5+
template <class _CharT, class _Traits = void> class basic_ostream {
6+
public:
7+
basic_ostream &operator<<(int);
8+
basic_ostream &operator<<(unsigned int);
9+
};
10+
11+
template <class CharT, class Traits>
12+
basic_ostream<CharT, Traits> &operator<<(basic_ostream<CharT, Traits> &, CharT);
13+
template <class CharT, class Traits>
14+
basic_ostream<CharT, Traits> &operator<<(basic_ostream<CharT, Traits> &, char);
15+
template <class _Traits>
16+
basic_ostream<char, _Traits> &operator<<(basic_ostream<char, _Traits> &, char);
17+
template <class _Traits>
18+
basic_ostream<char, _Traits> &operator<<(basic_ostream<char, _Traits> &,
19+
signed char);
20+
template <class _Traits>
21+
basic_ostream<char, _Traits> &operator<<(basic_ostream<char, _Traits> &,
22+
unsigned char);
23+
24+
using ostream = basic_ostream<char>;
25+
26+
27+
} // namespace std
28+
29+
class A : public std::ostream {};
30+
31+
void origin_ostream(std::ostream &os) {
32+
unsigned char unsigned_value = 9;
33+
os << unsigned_value;
34+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: ('unsigned char' passed to
35+
// 'operator<<' outputs as character instead of integer
36+
37+
signed char signed_value = 9;
38+
os << signed_value;
39+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: ('signed char' passed to
40+
// 'operator<<' outputs as character instead of integer
41+
42+
char char_value = 9;
43+
os << char_value;
44+
}
45+
46+
void based_on_ostream(A &os) {
47+
unsigned char unsigned_value = 9;
48+
os << unsigned_value;
49+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: ('unsigned char' passed to
50+
// 'operator<<' outputs as character instead of integer
51+
52+
signed char signed_value = 9;
53+
os << signed_value;
54+
// CHECK-MESSAGES: [[@LINE-1]]:6: warning: ('signed char' passed to
55+
// 'operator<<' outputs as character instead of integer
56+
57+
char char_value = 9;
58+
os << char_value;
59+
}
60+
61+
void based_on_ostream(std::basic_ostream<unsigned char> &os) {
62+
unsigned char unsigned_value = 9;
63+
os << unsigned_value;
64+
65+
signed char signed_value = 9;
66+
os << signed_value;
67+
68+
char char_value = 9;
69+
os << char_value;
70+
}

0 commit comments

Comments
 (0)