blob: 555f8e11dff94b97c105112e8bf2c5ad7dcf1aac [file] [log] [blame]
Tamás Zolnai350da402020-01-04 14:05:09 +01001//===--- 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
14using namespace clang::ast_matchers;
15using namespace clang::ast_matchers::internal;
16
17namespace clang {
18namespace tidy {
19namespace bugprone {
20
Tamás Zolnai04410c52020-03-14 17:57:02 +010021static constexpr int UnsignedASCIIUpperBound = 127;
22
Tamás Zolnai350da402020-01-04 14:05:09 +010023static Matcher<TypedefDecl> hasAnyListedName(const std::string &Names) {
24 const std::vector<std::string> NameList =
25 utils::options::parseStringList(Names);
26 return hasAnyName(std::vector<StringRef>(NameList.begin(), NameList.end()));
27}
28
29SignedCharMisuseCheck::SignedCharMisuseCheck(StringRef Name,
30 ClangTidyContext *Context)
31 : ClangTidyCheck(Name, Context),
Tamás Zolnaifedd5262020-05-06 10:45:03 +020032 CharTypdefsToIgnoreList(Options.get("CharTypdefsToIgnore", "")),
33 DiagnoseSignedUnsignedCharComparisons(
34 Options.get("DiagnoseSignedUnsignedCharComparisons", true)) {}
Tamás Zolnai350da402020-01-04 14:05:09 +010035
36void SignedCharMisuseCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
37 Options.store(Opts, "CharTypdefsToIgnore", CharTypdefsToIgnoreList);
Tamás Zolnaifedd5262020-05-06 10:45:03 +020038 Options.store(Opts, "DiagnoseSignedUnsignedCharComparisons",
39 DiagnoseSignedUnsignedCharComparisons);
Tamás Zolnai350da402020-01-04 14:05:09 +010040}
41
Tamás Zolnai04410c52020-03-14 17:57:02 +010042// Create a matcher for char -> integer cast.
43BindableMatcher<clang::Stmt> SignedCharMisuseCheck::charCastExpression(
44 bool IsSigned, const Matcher<clang::QualType> &IntegerType,
45 const std::string &CastBindName) const {
Tamás Zolnai350da402020-01-04 14:05:09 +010046 // We can ignore typedefs which are some kind of integer types
47 // (e.g. typedef char sal_Int8). In this case, we don't need to
48 // worry about the misinterpretation of char values.
49 const auto IntTypedef = qualType(
50 hasDeclaration(typedefDecl(hasAnyListedName(CharTypdefsToIgnoreList))));
51
Tamás Zolnai04410c52020-03-14 17:57:02 +010052 auto CharTypeExpr = expr();
53 if (IsSigned) {
54 CharTypeExpr = expr(hasType(
55 qualType(isAnyCharacter(), isSignedInteger(), unless(IntTypedef))));
56 } else {
57 CharTypeExpr = expr(hasType(qualType(
58 isAnyCharacter(), unless(isSignedInteger()), unless(IntTypedef))));
59 }
Tamás Zolnai350da402020-01-04 14:05:09 +010060
Tamás Zolnai350da402020-01-04 14:05:09 +010061 const auto ImplicitCastExpr =
Tamás Zolnai04410c52020-03-14 17:57:02 +010062 implicitCastExpr(hasSourceExpression(CharTypeExpr),
Tamás Zolnai350da402020-01-04 14:05:09 +010063 hasImplicitDestinationType(IntegerType))
Tamás Zolnai04410c52020-03-14 17:57:02 +010064 .bind(CastBindName);
Tamás Zolnai350da402020-01-04 14:05:09 +010065
66 const auto CStyleCastExpr = cStyleCastExpr(has(ImplicitCastExpr));
67 const auto StaticCastExpr = cxxStaticCastExpr(has(ImplicitCastExpr));
68 const auto FunctionalCastExpr = cxxFunctionalCastExpr(has(ImplicitCastExpr));
69
70 // We catch any type of casts to an integer. We need to have these cast
71 // expressions explicitly to catch only those casts which are direct children
Tamás Zolnai04410c52020-03-14 17:57:02 +010072 // of the checked expressions. (e.g. assignment, declaration).
Stephen Kellya72307c2019-11-12 15:15:56 +000073 return traverse(ast_type_traits::TK_AsIs,
74 expr(anyOf(ImplicitCastExpr, CStyleCastExpr, StaticCastExpr,
75 FunctionalCastExpr)));
Tamás Zolnai04410c52020-03-14 17:57:02 +010076}
Tamás Zolnai350da402020-01-04 14:05:09 +010077
Tamás Zolnai04410c52020-03-14 17:57:02 +010078void SignedCharMisuseCheck::registerMatchers(MatchFinder *Finder) {
79 const auto IntegerType =
80 qualType(isInteger(), unless(isAnyCharacter()), unless(booleanType()))
81 .bind("integerType");
82 const auto SignedCharCastExpr =
83 charCastExpression(true, IntegerType, "signedCastExpression");
84 const auto UnSignedCharCastExpr =
85 charCastExpression(false, IntegerType, "unsignedCastExpression");
86
87 // Catch assignments with singed char -> integer conversion.
88 const auto AssignmentOperatorExpr =
89 expr(binaryOperator(hasOperatorName("="), hasLHS(hasType(IntegerType)),
90 hasRHS(SignedCharCastExpr)));
Tamás Zolnai350da402020-01-04 14:05:09 +010091
92 Finder->addMatcher(AssignmentOperatorExpr, this);
93
Tamás Zolnai04410c52020-03-14 17:57:02 +010094 // Catch declarations with singed char -> integer conversion.
95 const auto Declaration = varDecl(isDefinition(), hasType(IntegerType),
96 hasInitializer(SignedCharCastExpr));
Tamás Zolnai350da402020-01-04 14:05:09 +010097
98 Finder->addMatcher(Declaration, this);
Tamás Zolnai04410c52020-03-14 17:57:02 +010099
Tamás Zolnaifedd5262020-05-06 10:45:03 +0200100 if (DiagnoseSignedUnsignedCharComparisons) {
101 // Catch signed char/unsigned char comparison.
102 const auto CompareOperator =
103 expr(binaryOperator(hasAnyOperatorName("==", "!="),
104 anyOf(allOf(hasLHS(SignedCharCastExpr),
105 hasRHS(UnSignedCharCastExpr)),
106 allOf(hasLHS(UnSignedCharCastExpr),
107 hasRHS(SignedCharCastExpr)))))
108 .bind("comparison");
Tamás Zolnai04410c52020-03-14 17:57:02 +0100109
Tamás Zolnaifedd5262020-05-06 10:45:03 +0200110 Finder->addMatcher(CompareOperator, this);
111 }
Tamás Zolnai030ff902020-05-02 14:04:17 +0200112
113 // Catch array subscripts with signed char -> integer conversion.
114 // Matcher for C arrays.
115 const auto CArraySubscript =
116 arraySubscriptExpr(hasIndex(SignedCharCastExpr)).bind("arraySubscript");
117
118 Finder->addMatcher(CArraySubscript, this);
119
120 // Matcher for std arrays.
121 const auto STDArraySubscript =
122 cxxOperatorCallExpr(
123 hasOverloadedOperatorName("[]"),
124 hasArgument(0, hasType(cxxRecordDecl(hasName("::std::array")))),
125 hasArgument(1, SignedCharCastExpr))
126 .bind("arraySubscript");
127
128 Finder->addMatcher(STDArraySubscript, this);
Tamás Zolnai350da402020-01-04 14:05:09 +0100129}
130
131void SignedCharMisuseCheck::check(const MatchFinder::MatchResult &Result) {
Tamás Zolnai04410c52020-03-14 17:57:02 +0100132 const auto *SignedCastExpression =
133 Result.Nodes.getNodeAs<ImplicitCastExpr>("signedCastExpression");
Tamás Zolnai030ff902020-05-02 14:04:17 +0200134 const auto *IntegerType = Result.Nodes.getNodeAs<QualType>("integerType");
135 assert(SignedCastExpression);
136 assert(IntegerType);
Tamás Zolnai350da402020-01-04 14:05:09 +0100137
Tamás Zolnai04410c52020-03-14 17:57:02 +0100138 // Ignore the match if we know that the signed char's value is not negative.
Tamás Zolnai350da402020-01-04 14:05:09 +0100139 // The potential misinterpretation happens for negative values only.
140 Expr::EvalResult EVResult;
Tamás Zolnai04410c52020-03-14 17:57:02 +0100141 if (!SignedCastExpression->isValueDependent() &&
142 SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,
143 *Result.Context)) {
144 llvm::APSInt Value = EVResult.Val.getInt();
145 if (Value.isNonNegative())
Tamás Zolnai350da402020-01-04 14:05:09 +0100146 return;
147 }
148
Tamás Zolnai04410c52020-03-14 17:57:02 +0100149 if (const auto *Comparison = Result.Nodes.getNodeAs<Expr>("comparison")) {
150 const auto *UnSignedCastExpression =
151 Result.Nodes.getNodeAs<ImplicitCastExpr>("unsignedCastExpression");
152
153 // We can ignore the ASCII value range also for unsigned char.
154 Expr::EvalResult EVResult;
155 if (!UnSignedCastExpression->isValueDependent() &&
156 UnSignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,
157 *Result.Context)) {
158 llvm::APSInt Value = EVResult.Val.getInt();
159 if (Value <= UnsignedASCIIUpperBound)
160 return;
161 }
162
163 diag(Comparison->getBeginLoc(),
164 "comparison between 'signed char' and 'unsigned char'");
Tamás Zolnai030ff902020-05-02 14:04:17 +0200165 } else if (Result.Nodes.getNodeAs<Expr>("arraySubscript")) {
166 diag(SignedCastExpression->getBeginLoc(),
167 "'signed char' to %0 conversion in array subscript; "
168 "consider casting to 'unsigned char' first.")
169 << *IntegerType;
170 } else {
Tamás Zolnai04410c52020-03-14 17:57:02 +0100171 diag(SignedCastExpression->getBeginLoc(),
172 "'signed char' to %0 conversion; "
173 "consider casting to 'unsigned char' first.")
174 << *IntegerType;
Tamás Zolnai030ff902020-05-02 14:04:17 +0200175 }
Tamás Zolnai350da402020-01-04 14:05:09 +0100176}
177
178} // namespace bugprone
179} // namespace tidy
180} // namespace clang