• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
hasAnyListedName(const std::string & Names)23 static 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 
SignedCharMisuseCheck(StringRef Name,ClangTidyContext * Context)29 SignedCharMisuseCheck::SignedCharMisuseCheck(StringRef Name,
30                                              ClangTidyContext *Context)
31     : ClangTidyCheck(Name, Context),
32       CharTypdefsToIgnoreList(Options.get("CharTypdefsToIgnore", "")),
33       DiagnoseSignedUnsignedCharComparisons(
34           Options.get("DiagnoseSignedUnsignedCharComparisons", true)) {}
35 
storeOptions(ClangTidyOptions::OptionMap & Opts)36 void SignedCharMisuseCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
37   Options.store(Opts, "CharTypdefsToIgnore", CharTypdefsToIgnoreList);
38   Options.store(Opts, "DiagnoseSignedUnsignedCharComparisons",
39                 DiagnoseSignedUnsignedCharComparisons);
40 }
41 
42 // Create a matcher for char -> integer cast.
charCastExpression(bool IsSigned,const Matcher<clang::QualType> & IntegerType,const std::string & CastBindName) const43 BindableMatcher<clang::Stmt> SignedCharMisuseCheck::charCastExpression(
44     bool IsSigned, const Matcher<clang::QualType> &IntegerType,
45     const std::string &CastBindName) const {
46   // 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 
52   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   }
60 
61   const auto ImplicitCastExpr =
62       implicitCastExpr(hasSourceExpression(CharTypeExpr),
63                        hasImplicitDestinationType(IntegerType))
64           .bind(CastBindName);
65 
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
72   // of the checked expressions. (e.g. assignment, declaration).
73   return traverse(ast_type_traits::TK_AsIs,
74                   expr(anyOf(ImplicitCastExpr, CStyleCastExpr, StaticCastExpr,
75                              FunctionalCastExpr)));
76 }
77 
registerMatchers(MatchFinder * Finder)78 void 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 signed char -> integer conversion.
88   const auto AssignmentOperatorExpr =
89       expr(binaryOperator(hasOperatorName("="), hasLHS(hasType(IntegerType)),
90                           hasRHS(SignedCharCastExpr)));
91 
92   Finder->addMatcher(AssignmentOperatorExpr, this);
93 
94   // Catch declarations with signed char -> integer conversion.
95   const auto Declaration = varDecl(isDefinition(), hasType(IntegerType),
96                                    hasInitializer(SignedCharCastExpr));
97 
98   Finder->addMatcher(Declaration, this);
99 
100   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");
109 
110     Finder->addMatcher(CompareOperator, this);
111   }
112 
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);
129 }
130 
check(const MatchFinder::MatchResult & Result)131 void SignedCharMisuseCheck::check(const MatchFinder::MatchResult &Result) {
132   const auto *SignedCastExpression =
133       Result.Nodes.getNodeAs<ImplicitCastExpr>("signedCastExpression");
134   const auto *IntegerType = Result.Nodes.getNodeAs<QualType>("integerType");
135   assert(SignedCastExpression);
136   assert(IntegerType);
137 
138   // Ignore the match if we know that the signed char's value is not negative.
139   // The potential misinterpretation happens for negative values only.
140   Expr::EvalResult EVResult;
141   if (!SignedCastExpression->isValueDependent() &&
142       SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,
143                                                         *Result.Context)) {
144     llvm::APSInt Value = EVResult.Val.getInt();
145     if (Value.isNonNegative())
146       return;
147   }
148 
149   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'");
165   } 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 {
171     diag(SignedCastExpression->getBeginLoc(),
172          "'signed char' to %0 conversion; "
173          "consider casting to 'unsigned char' first.")
174         << *IntegerType;
175   }
176 }
177 
178 } // namespace bugprone
179 } // namespace tidy
180 } // namespace clang
181