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), |
| 32 | CharTypdefsToIgnoreList(Options.get("CharTypdefsToIgnore", "")) {} |
| 33 | |
| 34 | void SignedCharMisuseCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
| 35 | Options.store(Opts, "CharTypdefsToIgnore", CharTypdefsToIgnoreList); |
| 36 | } |
| 37 | |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame^] | 38 | // Create a matcher for char -> integer cast. |
| 39 | BindableMatcher<clang::Stmt> SignedCharMisuseCheck::charCastExpression( |
| 40 | bool IsSigned, const Matcher<clang::QualType> &IntegerType, |
| 41 | const std::string &CastBindName) const { |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 42 | // We can ignore typedefs which are some kind of integer types |
| 43 | // (e.g. typedef char sal_Int8). In this case, we don't need to |
| 44 | // worry about the misinterpretation of char values. |
| 45 | const auto IntTypedef = qualType( |
| 46 | hasDeclaration(typedefDecl(hasAnyListedName(CharTypdefsToIgnoreList)))); |
| 47 | |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame^] | 48 | auto CharTypeExpr = expr(); |
| 49 | if (IsSigned) { |
| 50 | CharTypeExpr = expr(hasType( |
| 51 | qualType(isAnyCharacter(), isSignedInteger(), unless(IntTypedef)))); |
| 52 | } else { |
| 53 | CharTypeExpr = expr(hasType(qualType( |
| 54 | isAnyCharacter(), unless(isSignedInteger()), unless(IntTypedef)))); |
| 55 | } |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 56 | |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 57 | const auto ImplicitCastExpr = |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame^] | 58 | implicitCastExpr(hasSourceExpression(CharTypeExpr), |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 59 | hasImplicitDestinationType(IntegerType)) |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame^] | 60 | .bind(CastBindName); |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 61 | |
| 62 | const auto CStyleCastExpr = cStyleCastExpr(has(ImplicitCastExpr)); |
| 63 | const auto StaticCastExpr = cxxStaticCastExpr(has(ImplicitCastExpr)); |
| 64 | const auto FunctionalCastExpr = cxxFunctionalCastExpr(has(ImplicitCastExpr)); |
| 65 | |
| 66 | // We catch any type of casts to an integer. We need to have these cast |
| 67 | // expressions explicitly to catch only those casts which are direct children |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame^] | 68 | // of the checked expressions. (e.g. assignment, declaration). |
| 69 | return expr(anyOf(ImplicitCastExpr, CStyleCastExpr, StaticCastExpr, |
| 70 | FunctionalCastExpr)); |
| 71 | } |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 72 | |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame^] | 73 | void SignedCharMisuseCheck::registerMatchers(MatchFinder *Finder) { |
| 74 | const auto IntegerType = |
| 75 | qualType(isInteger(), unless(isAnyCharacter()), unless(booleanType())) |
| 76 | .bind("integerType"); |
| 77 | const auto SignedCharCastExpr = |
| 78 | charCastExpression(true, IntegerType, "signedCastExpression"); |
| 79 | const auto UnSignedCharCastExpr = |
| 80 | charCastExpression(false, IntegerType, "unsignedCastExpression"); |
| 81 | |
| 82 | // Catch assignments with singed char -> integer conversion. |
| 83 | const auto AssignmentOperatorExpr = |
| 84 | expr(binaryOperator(hasOperatorName("="), hasLHS(hasType(IntegerType)), |
| 85 | hasRHS(SignedCharCastExpr))); |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 86 | |
| 87 | Finder->addMatcher(AssignmentOperatorExpr, this); |
| 88 | |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame^] | 89 | // Catch declarations with singed char -> integer conversion. |
| 90 | const auto Declaration = varDecl(isDefinition(), hasType(IntegerType), |
| 91 | hasInitializer(SignedCharCastExpr)); |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 92 | |
| 93 | Finder->addMatcher(Declaration, this); |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame^] | 94 | |
| 95 | // Catch signed char/unsigned char comparison. |
| 96 | const auto CompareOperator = |
| 97 | expr(binaryOperator(hasAnyOperatorName("==", "!="), |
| 98 | anyOf(allOf(hasLHS(SignedCharCastExpr), |
| 99 | hasRHS(UnSignedCharCastExpr)), |
| 100 | allOf(hasLHS(UnSignedCharCastExpr), |
| 101 | hasRHS(SignedCharCastExpr))))) |
| 102 | .bind("comparison"); |
| 103 | |
| 104 | Finder->addMatcher(CompareOperator, this); |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 105 | } |
| 106 | |
| 107 | void SignedCharMisuseCheck::check(const MatchFinder::MatchResult &Result) { |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame^] | 108 | const auto *SignedCastExpression = |
| 109 | Result.Nodes.getNodeAs<ImplicitCastExpr>("signedCastExpression"); |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 110 | |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame^] | 111 | // 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] | 112 | // The potential misinterpretation happens for negative values only. |
| 113 | Expr::EvalResult EVResult; |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame^] | 114 | if (!SignedCastExpression->isValueDependent() && |
| 115 | SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult, |
| 116 | *Result.Context)) { |
| 117 | llvm::APSInt Value = EVResult.Val.getInt(); |
| 118 | if (Value.isNonNegative()) |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 119 | return; |
| 120 | } |
| 121 | |
Tamás Zolnai | 04410c5 | 2020-03-14 17:57:02 +0100 | [diff] [blame^] | 122 | if (const auto *Comparison = Result.Nodes.getNodeAs<Expr>("comparison")) { |
| 123 | const auto *UnSignedCastExpression = |
| 124 | Result.Nodes.getNodeAs<ImplicitCastExpr>("unsignedCastExpression"); |
| 125 | |
| 126 | // We can ignore the ASCII value range also for unsigned char. |
| 127 | Expr::EvalResult EVResult; |
| 128 | if (!UnSignedCastExpression->isValueDependent() && |
| 129 | UnSignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult, |
| 130 | *Result.Context)) { |
| 131 | llvm::APSInt Value = EVResult.Val.getInt(); |
| 132 | if (Value <= UnsignedASCIIUpperBound) |
| 133 | return; |
| 134 | } |
| 135 | |
| 136 | diag(Comparison->getBeginLoc(), |
| 137 | "comparison between 'signed char' and 'unsigned char'"); |
| 138 | } else if (const auto *IntegerType = |
| 139 | Result.Nodes.getNodeAs<QualType>("integerType")) { |
| 140 | diag(SignedCastExpression->getBeginLoc(), |
| 141 | "'signed char' to %0 conversion; " |
| 142 | "consider casting to 'unsigned char' first.") |
| 143 | << *IntegerType; |
| 144 | } else |
| 145 | llvm_unreachable("Unexpected match"); |
Tamás Zolnai | 350da40 | 2020-01-04 14:05:09 +0100 | [diff] [blame] | 146 | } |
| 147 | |
| 148 | } // namespace bugprone |
| 149 | } // namespace tidy |
| 150 | } // namespace clang |