1 //===--- SignedCharMisuseCheck.cpp - clang-tidy ---------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "SignedCharMisuseCheck.h"
10 #include "../utils/OptionsUtils.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 using namespace clang::ast_matchers
;
15 using namespace clang::ast_matchers::internal
;
17 namespace clang::tidy::bugprone
{
19 static constexpr int UnsignedASCIIUpperBound
= 127;
21 SignedCharMisuseCheck::SignedCharMisuseCheck(StringRef Name
,
22 ClangTidyContext
*Context
)
23 : ClangTidyCheck(Name
, Context
),
24 CharTypdefsToIgnoreList(Options
.get("CharTypdefsToIgnore", "")),
25 DiagnoseSignedUnsignedCharComparisons(
26 Options
.get("DiagnoseSignedUnsignedCharComparisons", true)) {}
28 void SignedCharMisuseCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
29 Options
.store(Opts
, "CharTypdefsToIgnore", CharTypdefsToIgnoreList
);
30 Options
.store(Opts
, "DiagnoseSignedUnsignedCharComparisons",
31 DiagnoseSignedUnsignedCharComparisons
);
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(typedefDecl(
42 hasAnyName(utils::options::parseStringList(CharTypdefsToIgnoreList
)))));
44 auto CharTypeExpr
= expr();
46 CharTypeExpr
= expr(hasType(
47 qualType(isAnyCharacter(), isSignedInteger(), unless(IntTypedef
))));
49 CharTypeExpr
= expr(hasType(qualType(
50 isAnyCharacter(), unless(isSignedInteger()), unless(IntTypedef
))));
53 const auto ImplicitCastExpr
=
54 implicitCastExpr(hasSourceExpression(CharTypeExpr
),
55 hasImplicitDestinationType(IntegerType
))
58 const auto CStyleCastExpr
= cStyleCastExpr(has(ImplicitCastExpr
));
59 const auto StaticCastExpr
= cxxStaticCastExpr(has(ImplicitCastExpr
));
60 const auto FunctionalCastExpr
= cxxFunctionalCastExpr(has(ImplicitCastExpr
));
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_AsIs
, expr(anyOf(ImplicitCastExpr
, CStyleCastExpr
,
66 StaticCastExpr
, FunctionalCastExpr
)));
69 void SignedCharMisuseCheck::registerMatchers(MatchFinder
*Finder
) {
70 const auto IntegerType
=
71 qualType(isInteger(), unless(isAnyCharacter()), unless(booleanType()))
73 const auto SignedCharCastExpr
=
74 charCastExpression(true, IntegerType
, "signedCastExpression");
75 const auto UnSignedCharCastExpr
=
76 charCastExpression(false, IntegerType
, "unsignedCastExpression");
78 // Catch assignments with signed char -> integer conversion.
79 const auto AssignmentOperatorExpr
=
80 expr(binaryOperator(hasOperatorName("="), hasLHS(hasType(IntegerType
)),
81 hasRHS(SignedCharCastExpr
)));
83 Finder
->addMatcher(AssignmentOperatorExpr
, this);
85 // Catch declarations with signed char -> integer conversion.
86 const auto Declaration
= varDecl(isDefinition(), hasType(IntegerType
),
87 hasInitializer(SignedCharCastExpr
));
89 Finder
->addMatcher(Declaration
, this);
91 if (DiagnoseSignedUnsignedCharComparisons
) {
92 // Catch signed char/unsigned char comparison.
93 const auto CompareOperator
=
94 expr(binaryOperator(hasAnyOperatorName("==", "!="),
95 anyOf(allOf(hasLHS(SignedCharCastExpr
),
96 hasRHS(UnSignedCharCastExpr
)),
97 allOf(hasLHS(UnSignedCharCastExpr
),
98 hasRHS(SignedCharCastExpr
)))))
101 Finder
->addMatcher(CompareOperator
, this);
104 // Catch array subscripts with signed char -> integer conversion.
105 // Matcher for C arrays.
106 const auto CArraySubscript
=
107 arraySubscriptExpr(hasIndex(SignedCharCastExpr
)).bind("arraySubscript");
109 Finder
->addMatcher(CArraySubscript
, this);
111 // Matcher for std arrays.
112 const auto STDArraySubscript
=
114 hasOverloadedOperatorName("[]"),
115 hasArgument(0, hasType(cxxRecordDecl(hasName("::std::array")))),
116 hasArgument(1, SignedCharCastExpr
))
117 .bind("arraySubscript");
119 Finder
->addMatcher(STDArraySubscript
, this);
122 void SignedCharMisuseCheck::check(const MatchFinder::MatchResult
&Result
) {
123 const auto *SignedCastExpression
=
124 Result
.Nodes
.getNodeAs
<ImplicitCastExpr
>("signedCastExpression");
125 const auto *IntegerType
= Result
.Nodes
.getNodeAs
<QualType
>("integerType");
126 assert(SignedCastExpression
);
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
,
135 llvm::APSInt Value
= EVResult
.Val
.getInt();
136 if (Value
.isNonNegative())
140 if (const auto *Comparison
= Result
.Nodes
.getNodeAs
<Expr
>("comparison")) {
141 const auto *UnSignedCastExpression
=
142 Result
.Nodes
.getNodeAs
<ImplicitCastExpr
>("unsignedCastExpression");
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
,
149 llvm::APSInt Value
= EVResult
.Val
.getInt();
150 if (Value
<= UnsignedASCIIUpperBound
)
154 diag(Comparison
->getBeginLoc(),
155 "comparison between 'signed char' and 'unsigned char'");
156 } else if (Result
.Nodes
.getNodeAs
<Expr
>("arraySubscript")) {
157 diag(SignedCastExpression
->getBeginLoc(),
158 "'signed char' to %0 conversion in array subscript; "
159 "consider casting to 'unsigned char' first.")
162 diag(SignedCastExpression
->getBeginLoc(),
163 "'signed char' to %0 conversion; "
164 "consider casting to 'unsigned char' first.")
169 } // namespace clang::tidy::bugprone