Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 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 | |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 21 | static constexpr int UnsignedASCIIUpperBound = 127; |
| 22 | |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 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 | |
| 29 | SignedCharMisuseCheck::SignedCharMisuseCheck(StringRef Name, |
| 30 | ClangTidyContext *Context) |
| 31 | : ClangTidyCheck(Name, Context), |
Tamás Zolnai | fedd526 | 2020-05-06 10:45:03 +0200 | [diff] [blame] | 32 | CharTypdefsToIgnoreList(Options.get("CharTypdefsToIgnore", "")), |
| 33 | DiagnoseSignedUnsignedCharComparisons( |
| 34 | Options.get("DiagnoseSignedUnsignedCharComparisons", true)) {} |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 35 | |
| 36 | void SignedCharMisuseCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
| 37 | Options.store(Opts, "CharTypdefsToIgnore", CharTypdefsToIgnoreList); |
Tamás Zolnai | fedd526 | 2020-05-06 10:45:03 +0200 | [diff] [blame] | 38 | Options.store(Opts, "DiagnoseSignedUnsignedCharComparisons", |
| 39 | DiagnoseSignedUnsignedCharComparisons); |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 40 | } |
| 41 | |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 42 | // Create a matcher for char -> integer cast. |
| 43 | BindableMatcher<clang::Stmt> SignedCharMisuseCheck::charCastExpression( |
| 44 | bool IsSigned, const Matcher<clang::QualType> &IntegerType, |
| 45 | const std::string &CastBindName) const { |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 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 | |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 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 | } |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 60 | |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 61 | const auto ImplicitCastExpr = |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 62 | implicitCastExpr(hasSourceExpression(CharTypeExpr), |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 63 | hasImplicitDestinationType(IntegerType)) |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 64 | .bind(CastBindName); |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 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 |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 72 | // of the checked expressions. (e.g. assignment, declaration). |
Stephen Kelly | a72307c | 2019-11-12 15:15:56 +0000 | [diff] [blame] | 73 | return traverse(ast_type_traits::TK_AsIs, |
| 74 | expr(anyOf(ImplicitCastExpr, CStyleCastExpr, StaticCastExpr, |
| 75 | FunctionalCastExpr))); |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 76 | } |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 77 | |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 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 singed char -> integer conversion. |
| 88 | const auto AssignmentOperatorExpr = |
| 89 | expr(binaryOperator(hasOperatorName("="), hasLHS(hasType(IntegerType)), |
| 90 | hasRHS(SignedCharCastExpr))); |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 91 | |
| 92 | Finder->addMatcher(AssignmentOperatorExpr, this); |
| 93 | |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 94 | // Catch declarations with singed char -> integer conversion. |
| 95 | const auto Declaration = varDecl(isDefinition(), hasType(IntegerType), |
| 96 | hasInitializer(SignedCharCastExpr)); |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 97 | |
| 98 | Finder->addMatcher(Declaration, this); |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 99 | |
Tamás Zolnai | fedd526 | 2020-05-06 10:45:03 +0200 | [diff] [blame] | 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"); |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 109 | |
Tamás Zolnai | fedd526 | 2020-05-06 10:45:03 +0200 | [diff] [blame] | 110 | Finder->addMatcher(CompareOperator, this); |
| 111 | } |
Tamás Zolnai | 030ff90 | 2020-05-02 14:04:17 +0200 | [diff] [blame] | 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); |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 129 | } |
| 130 | |
| 131 | void SignedCharMisuseCheck::check(const MatchFinder::MatchResult &Result) { |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 132 | const auto *SignedCastExpression = |
| 133 | Result.Nodes.getNodeAs<ImplicitCastExpr>("signedCastExpression"); |
Tamás Zolnai | 030ff90 | 2020-05-02 14:04:17 +0200 | [diff] [blame] | 134 | const auto *IntegerType = Result.Nodes.getNodeAs<QualType>("integerType"); |
| 135 | assert(SignedCastExpression); |
| 136 | assert(IntegerType); |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 137 | |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 138 | // Ignore the match if we know that the signed char's value is not negative. |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 139 | // The potential misinterpretation happens for negative values only. |
| 140 | Expr::EvalResult EVResult; |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 141 | if (!SignedCastExpression->isValueDependent() && |
| 142 | SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult, |
| 143 | *Result.Context)) { |
| 144 | llvm::APSInt Value = EVResult.Val.getInt(); |
| 145 | if (Value.isNonNegative()) |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 146 | return; |
| 147 | } |
| 148 | |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 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'"); |
Tamás Zolnai | 030ff90 | 2020-05-02 14:04:17 +0200 | [diff] [blame] | 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 { |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame] | 171 | diag(SignedCastExpression->getBeginLoc(), |
| 172 | "'signed char' to %0 conversion; " |
| 173 | "consider casting to 'unsigned char' first.") |
| 174 | << *IntegerType; |
Tamás Zolnai | 030ff90 | 2020-05-02 14:04:17 +0200 | [diff] [blame] | 175 | } |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 176 | } |
| 177 | |
| 178 | } // namespace bugprone |
| 179 | } // namespace tidy |
| 180 | } // namespace clang |