1 | //===--- UnintendedCharOstreamOutputCheck.cpp - clang-tidy ----------------===// |
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 | #include "UnintendedCharOstreamOutputCheck.h" |
10 | #include "../utils/Matchers.h" |
11 | #include "../utils/OptionsUtils.h" |
12 | #include "clang/AST/Type.h" |
13 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
14 | #include "clang/ASTMatchers/ASTMatchers.h" |
15 | #include "clang/Basic/Diagnostic.h" |
16 | #include "clang/Tooling/FixIt.h" |
17 | |
18 | using namespace clang::ast_matchers; |
19 | |
20 | namespace clang::tidy::bugprone { |
21 | |
22 | namespace { |
23 | |
24 | // check if the type is unsigned char or signed char |
25 | AST_MATCHER(Type, isNumericChar) { |
26 | return Node.isSpecificBuiltinType(K: BuiltinType::SChar) || |
27 | Node.isSpecificBuiltinType(K: BuiltinType::UChar); |
28 | } |
29 | |
30 | // check if the type is char |
31 | AST_MATCHER(Type, isChar) { |
32 | return Node.isSpecificBuiltinType(K: BuiltinType::Char_S) || |
33 | Node.isSpecificBuiltinType(K: BuiltinType::Char_U); |
34 | } |
35 | |
36 | } // namespace |
37 | |
38 | UnintendedCharOstreamOutputCheck::UnintendedCharOstreamOutputCheck( |
39 | StringRef Name, ClangTidyContext *Context) |
40 | : ClangTidyCheck(Name, Context), |
41 | AllowedTypes(utils::options::parseStringList( |
42 | Option: Options.get(LocalName: "AllowedTypes" , Default: "unsigned char;signed char" ))), |
43 | CastTypeName(Options.get(LocalName: "CastTypeName" )) {} |
44 | void UnintendedCharOstreamOutputCheck::storeOptions( |
45 | ClangTidyOptions::OptionMap &Opts) { |
46 | Options.store(Options&: Opts, LocalName: "AllowedTypes" , |
47 | Value: utils::options::serializeStringList(Strings: AllowedTypes)); |
48 | if (CastTypeName.has_value()) |
49 | Options.store(Options&: Opts, LocalName: "CastTypeName" , Value: CastTypeName.value()); |
50 | } |
51 | |
52 | void UnintendedCharOstreamOutputCheck::registerMatchers(MatchFinder *Finder) { |
53 | auto BasicOstream = |
54 | cxxRecordDecl(hasName(Name: "::std::basic_ostream" ), |
55 | // only basic_ostream<char, Traits> has overload operator<< |
56 | // with char / unsigned char / signed char |
57 | classTemplateSpecializationDecl( |
58 | hasTemplateArgument(N: 0, InnerMatcher: refersToType(InnerMatcher: isChar())))); |
59 | auto IsDeclRefExprFromAllowedTypes = declRefExpr(to(InnerMatcher: varDecl( |
60 | hasType(InnerMatcher: matchers::matchesAnyListedTypeName(NameList: AllowedTypes, CanonicalTypes: false))))); |
61 | auto IsExplicitCastExprFromAllowedTypes = explicitCastExpr(hasDestinationType( |
62 | InnerMatcher: matchers::matchesAnyListedTypeName(NameList: AllowedTypes, CanonicalTypes: false))); |
63 | Finder->addMatcher( |
64 | NodeMatch: cxxOperatorCallExpr( |
65 | hasOverloadedOperatorName(Name: "<<" ), |
66 | hasLHS(InnerMatcher: hasType(InnerMatcher: hasUnqualifiedDesugaredType( |
67 | InnerMatcher: recordType(hasDeclaration(InnerMatcher: cxxRecordDecl( |
68 | anyOf(BasicOstream, isDerivedFrom(Base: BasicOstream)))))))), |
69 | hasRHS(InnerMatcher: expr(hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: isNumericChar())), |
70 | unless(ignoringParenImpCasts( |
71 | InnerMatcher: anyOf(IsDeclRefExprFromAllowedTypes, |
72 | IsExplicitCastExprFromAllowedTypes)))))) |
73 | .bind(ID: "x" ), |
74 | Action: this); |
75 | } |
76 | |
77 | void UnintendedCharOstreamOutputCheck::check( |
78 | const MatchFinder::MatchResult &Result) { |
79 | const auto *Call = Result.Nodes.getNodeAs<CXXOperatorCallExpr>(ID: "x" ); |
80 | const Expr *Value = Call->getArg(1); |
81 | const SourceRange SourceRange = Value->getSourceRange(); |
82 | |
83 | DiagnosticBuilder Builder = |
84 | diag(Loc: Call->getOperatorLoc(), |
85 | Description: "%0 passed to 'operator<<' outputs as character instead of integer. " |
86 | "cast to 'unsigned int' to print numeric value or cast to 'char' to " |
87 | "print as character" ) |
88 | << Value->getType() << SourceRange; |
89 | |
90 | QualType T = Value->getType(); |
91 | const Type *UnqualifiedDesugaredType = T->getUnqualifiedDesugaredType(); |
92 | |
93 | llvm::StringRef CastType = CastTypeName.value_or( |
94 | u: UnqualifiedDesugaredType->isSpecificBuiltinType(K: BuiltinType::SChar) |
95 | ? "int" |
96 | : "unsigned int" ); |
97 | |
98 | Builder << FixItHint::CreateReplacement( |
99 | RemoveRange: SourceRange, Code: ("static_cast<" + CastType + ">(" + |
100 | tooling::fixit::getText(Node: *Value, Context: *Result.Context) + ")" ) |
101 | .str()); |
102 | } |
103 | |
104 | } // namespace clang::tidy::bugprone |
105 | |