xref: /llvm-project/clang-tools-extra/clang-tidy/bugprone/SignedCharMisuseCheck.cpp (revision 7d2ea6c422d3f5712b7253407005e1a465a76946)
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 
SignedCharMisuseCheck(StringRef Name,ClangTidyContext * Context)21 SignedCharMisuseCheck::SignedCharMisuseCheck(StringRef Name,
22                                              ClangTidyContext *Context)
23     : ClangTidyCheck(Name, Context),
24       CharTypdefsToIgnoreList(Options.get("CharTypdefsToIgnore", "")),
25       DiagnoseSignedUnsignedCharComparisons(
26           Options.get("DiagnoseSignedUnsignedCharComparisons", true)) {}
27 
storeOptions(ClangTidyOptions::OptionMap & Opts)28 void SignedCharMisuseCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
29   Options.store(Opts, "CharTypdefsToIgnore", CharTypdefsToIgnoreList);
30   Options.store(Opts, "DiagnoseSignedUnsignedCharComparisons",
31                 DiagnoseSignedUnsignedCharComparisons);
32 }
33 
34 // Create a matcher for char -> integer cast.
charCastExpression(bool IsSigned,const Matcher<clang::QualType> & IntegerType,const std::string & CastBindName) const35 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)))));
43 
44   auto CharTypeExpr = expr();
45   if (IsSigned) {
46     CharTypeExpr = expr(hasType(
47         qualType(isAnyCharacter(), isSignedInteger(), unless(IntTypedef))));
48   } else {
49     CharTypeExpr = expr(hasType(qualType(
50         isAnyCharacter(), unless(isSignedInteger()), unless(IntTypedef))));
51   }
52 
53   const auto ImplicitCastExpr =
54       implicitCastExpr(hasSourceExpression(CharTypeExpr),
55                        hasImplicitDestinationType(IntegerType))
56           .bind(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_AsIs, expr(anyOf(ImplicitCastExpr, CStyleCastExpr,
66                                       StaticCastExpr, FunctionalCastExpr)));
67 }
68 
registerMatchers(MatchFinder * Finder)69 void SignedCharMisuseCheck::registerMatchers(MatchFinder *Finder) {
70   const auto IntegerType =
71       qualType(isInteger(), unless(isAnyCharacter()), unless(booleanType()))
72           .bind("integerType");
73   const auto SignedCharCastExpr =
74       charCastExpression(true, IntegerType, "signedCastExpression");
75   const auto UnSignedCharCastExpr =
76       charCastExpression(false, IntegerType, "unsignedCastExpression");
77 
78   // Catch assignments with signed char -> integer conversion.
79   const auto AssignmentOperatorExpr =
80       expr(binaryOperator(hasOperatorName("="), hasLHS(hasType(IntegerType)),
81                           hasRHS(SignedCharCastExpr)));
82 
83   Finder->addMatcher(AssignmentOperatorExpr, this);
84 
85   // Catch declarations with signed char -> integer conversion.
86   const auto Declaration = varDecl(isDefinition(), hasType(IntegerType),
87                                    hasInitializer(SignedCharCastExpr));
88 
89   Finder->addMatcher(Declaration, this);
90 
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)))))
99             .bind("comparison");
100 
101     Finder->addMatcher(CompareOperator, this);
102   }
103 
104   // Catch array subscripts with signed char -> integer conversion.
105   // Matcher for C arrays.
106   const auto CArraySubscript =
107       arraySubscriptExpr(hasIndex(SignedCharCastExpr)).bind("arraySubscript");
108 
109   Finder->addMatcher(CArraySubscript, this);
110 
111   // Matcher for std arrays.
112   const auto STDArraySubscript =
113       cxxOperatorCallExpr(
114           hasOverloadedOperatorName("[]"),
115           hasArgument(0, hasType(cxxRecordDecl(hasName("::std::array")))),
116           hasArgument(1, SignedCharCastExpr))
117           .bind("arraySubscript");
118 
119   Finder->addMatcher(STDArraySubscript, this);
120 }
121 
check(const MatchFinder::MatchResult & Result)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);
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>("comparison")) {
141     const auto *UnSignedCastExpression =
142         Result.Nodes.getNodeAs<ImplicitCastExpr>("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>("arraySubscript")) {
157     diag(SignedCastExpression->getBeginLoc(),
158          "'signed char' to %0 conversion in array subscript; "
159          "consider casting to 'unsigned char' first.")
160         << *IntegerType;
161   } else {
162     diag(SignedCastExpression->getBeginLoc(),
163          "'signed char' to %0 conversion; "
164          "consider casting to 'unsigned char' first.")
165         << *IntegerType;
166   }
167 }
168 
169 } // namespace clang::tidy::bugprone
170