1 | //===--- SignedCharMisuseCheck.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 "SignedCharMisuseCheck.h" |
10 | #include "../utils/OptionsUtils.h" |
11 | #include "clang/AST/ASTContext.h" |
12 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
13 | |
14 | using namespace clang::ast_matchers; |
15 | using namespace clang::ast_matchers::internal; |
16 | |
17 | namespace clang::tidy::bugprone { |
18 | |
19 | static constexpr int UnsignedASCIIUpperBound = 127; |
20 | |
21 | SignedCharMisuseCheck::SignedCharMisuseCheck(StringRef Name, |
22 | ClangTidyContext *Context) |
23 | : ClangTidyCheck(Name, Context), |
24 | CharTypdefsToIgnoreList(Options.get(LocalName: "CharTypdefsToIgnore" , Default: "" )), |
25 | DiagnoseSignedUnsignedCharComparisons( |
26 | Options.get(LocalName: "DiagnoseSignedUnsignedCharComparisons" , Default: true)) {} |
27 | |
28 | void SignedCharMisuseCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
29 | Options.store(Options&: Opts, LocalName: "CharTypdefsToIgnore" , Value: CharTypdefsToIgnoreList); |
30 | Options.store(Options&: Opts, LocalName: "DiagnoseSignedUnsignedCharComparisons" , |
31 | Value: DiagnoseSignedUnsignedCharComparisons); |
32 | } |
33 | |
34 | // Create a matcher for char -> integer cast. |
35 | BindableMatcher<clang::Stmt> SignedCharMisuseCheck::charCastExpression( |
36 | bool IsSigned, const Matcher<clang::QualType> &IntegerType, |
37 | const std::string &CastBindName) const { |
38 | // We can ignore typedefs which are some kind of integer types |
39 | // (e.g. typedef char sal_Int8). In this case, we don't need to |
40 | // worry about the misinterpretation of char values. |
41 | const auto IntTypedef = qualType(hasDeclaration(InnerMatcher: typedefDecl( |
42 | hasAnyName(utils::options::parseStringList(Option: CharTypdefsToIgnoreList))))); |
43 | |
44 | auto CharTypeExpr = expr(); |
45 | if (IsSigned) { |
46 | CharTypeExpr = expr(hasType( |
47 | InnerMatcher: qualType(isAnyCharacter(), isSignedInteger(), unless(IntTypedef)))); |
48 | } else { |
49 | CharTypeExpr = expr(hasType(InnerMatcher: qualType( |
50 | isAnyCharacter(), unless(isSignedInteger()), unless(IntTypedef)))); |
51 | } |
52 | |
53 | const auto ImplicitCastExpr = |
54 | implicitCastExpr(hasSourceExpression(InnerMatcher: CharTypeExpr), |
55 | hasImplicitDestinationType(InnerMatcher: IntegerType)) |
56 | .bind(ID: CastBindName); |
57 | |
58 | const auto CStyleCastExpr = cStyleCastExpr(has(ImplicitCastExpr)); |
59 | const auto StaticCastExpr = cxxStaticCastExpr(has(ImplicitCastExpr)); |
60 | const auto FunctionalCastExpr = cxxFunctionalCastExpr(has(ImplicitCastExpr)); |
61 | |
62 | // We catch any type of casts to an integer. We need to have these cast |
63 | // expressions explicitly to catch only those casts which are direct children |
64 | // of the checked expressions. (e.g. assignment, declaration). |
65 | return traverse(TK: TK_AsIs, InnerMatcher: expr(anyOf(ImplicitCastExpr, CStyleCastExpr, |
66 | StaticCastExpr, FunctionalCastExpr))); |
67 | } |
68 | |
69 | void SignedCharMisuseCheck::registerMatchers(MatchFinder *Finder) { |
70 | const auto IntegerType = |
71 | qualType(isInteger(), unless(isAnyCharacter()), unless(booleanType())) |
72 | .bind(ID: "integerType" ); |
73 | const auto SignedCharCastExpr = |
74 | charCastExpression(IsSigned: true, IntegerType, CastBindName: "signedCastExpression" ); |
75 | const auto UnSignedCharCastExpr = |
76 | charCastExpression(IsSigned: false, IntegerType, CastBindName: "unsignedCastExpression" ); |
77 | |
78 | // Catch assignments with signed char -> integer conversion. |
79 | const auto AssignmentOperatorExpr = |
80 | expr(binaryOperator(hasOperatorName(Name: "=" ), hasLHS(InnerMatcher: hasType(InnerMatcher: IntegerType)), |
81 | hasRHS(InnerMatcher: SignedCharCastExpr))); |
82 | |
83 | Finder->addMatcher(NodeMatch: AssignmentOperatorExpr, Action: this); |
84 | |
85 | // Catch declarations with signed char -> integer conversion. |
86 | const auto Declaration = varDecl(isDefinition(), hasType(InnerMatcher: IntegerType), |
87 | hasInitializer(InnerMatcher: SignedCharCastExpr)); |
88 | |
89 | Finder->addMatcher(NodeMatch: Declaration, Action: this); |
90 | |
91 | if (DiagnoseSignedUnsignedCharComparisons) { |
92 | // Catch signed char/unsigned char comparison. |
93 | const auto CompareOperator = |
94 | expr(binaryOperator(hasAnyOperatorName("==" , "!=" ), |
95 | anyOf(allOf(hasLHS(InnerMatcher: SignedCharCastExpr), |
96 | hasRHS(InnerMatcher: UnSignedCharCastExpr)), |
97 | allOf(hasLHS(InnerMatcher: UnSignedCharCastExpr), |
98 | hasRHS(InnerMatcher: SignedCharCastExpr))))) |
99 | .bind(ID: "comparison" ); |
100 | |
101 | Finder->addMatcher(NodeMatch: CompareOperator, Action: this); |
102 | } |
103 | |
104 | // Catch array subscripts with signed char -> integer conversion. |
105 | // Matcher for C arrays. |
106 | const auto CArraySubscript = |
107 | arraySubscriptExpr(hasIndex(InnerMatcher: SignedCharCastExpr)).bind(ID: "arraySubscript" ); |
108 | |
109 | Finder->addMatcher(NodeMatch: CArraySubscript, Action: this); |
110 | |
111 | // Matcher for std arrays. |
112 | const auto STDArraySubscript = |
113 | cxxOperatorCallExpr( |
114 | hasOverloadedOperatorName(Name: "[]" ), |
115 | hasArgument(N: 0, InnerMatcher: hasType(InnerMatcher: cxxRecordDecl(hasName(Name: "::std::array" )))), |
116 | hasArgument(N: 1, InnerMatcher: SignedCharCastExpr)) |
117 | .bind(ID: "arraySubscript" ); |
118 | |
119 | Finder->addMatcher(NodeMatch: STDArraySubscript, Action: this); |
120 | } |
121 | |
122 | void SignedCharMisuseCheck::check(const MatchFinder::MatchResult &Result) { |
123 | const auto *SignedCastExpression = |
124 | Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "signedCastExpression" ); |
125 | const auto *IntegerType = Result.Nodes.getNodeAs<QualType>(ID: "integerType" ); |
126 | assert(SignedCastExpression); |
127 | assert(IntegerType); |
128 | |
129 | // Ignore the match if we know that the signed char's value is not negative. |
130 | // The potential misinterpretation happens for negative values only. |
131 | Expr::EvalResult EVResult; |
132 | if (!SignedCastExpression->isValueDependent() && |
133 | SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult, |
134 | *Result.Context)) { |
135 | llvm::APSInt Value = EVResult.Val.getInt(); |
136 | if (Value.isNonNegative()) |
137 | return; |
138 | } |
139 | |
140 | if (const auto *Comparison = Result.Nodes.getNodeAs<Expr>(ID: "comparison" )) { |
141 | const auto *UnSignedCastExpression = |
142 | Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "unsignedCastExpression" ); |
143 | |
144 | // We can ignore the ASCII value range also for unsigned char. |
145 | Expr::EvalResult EVResult; |
146 | if (!UnSignedCastExpression->isValueDependent() && |
147 | UnSignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult, |
148 | *Result.Context)) { |
149 | llvm::APSInt Value = EVResult.Val.getInt(); |
150 | if (Value <= UnsignedASCIIUpperBound) |
151 | return; |
152 | } |
153 | |
154 | diag(Comparison->getBeginLoc(), |
155 | "comparison between 'signed char' and 'unsigned char'" ); |
156 | } else if (Result.Nodes.getNodeAs<Expr>(ID: "arraySubscript" )) { |
157 | diag(Loc: SignedCastExpression->getBeginLoc(), |
158 | Description: "'signed char' to %0 conversion in array subscript; " |
159 | "consider casting to 'unsigned char' first." ) |
160 | << *IntegerType; |
161 | } else { |
162 | diag(Loc: SignedCastExpression->getBeginLoc(), |
163 | Description: "'signed char' to %0 conversion; " |
164 | "consider casting to 'unsigned char' first." ) |
165 | << *IntegerType; |
166 | } |
167 | } |
168 | |
169 | } // namespace clang::tidy::bugprone |
170 | |