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