| //===--- SignedCharMisuseCheck.cpp - clang-tidy ---------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "SignedCharMisuseCheck.h" |
| #include "../utils/OptionsUtils.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| |
| using namespace clang::ast_matchers; |
| using namespace clang::ast_matchers::internal; |
| |
| namespace clang { |
| namespace tidy { |
| namespace bugprone { |
| |
| static constexpr int UnsignedASCIIUpperBound = 127; |
| |
| static Matcher<TypedefDecl> hasAnyListedName(const std::string &Names) { |
| const std::vector<std::string> NameList = |
| utils::options::parseStringList(Names); |
| return hasAnyName(std::vector<StringRef>(NameList.begin(), NameList.end())); |
| } |
| |
| SignedCharMisuseCheck::SignedCharMisuseCheck(StringRef Name, |
| ClangTidyContext *Context) |
| : ClangTidyCheck(Name, Context), |
| CharTypdefsToIgnoreList(Options.get("CharTypdefsToIgnore", "")), |
| DiagnoseSignedUnsignedCharComparisons( |
| Options.get("DiagnoseSignedUnsignedCharComparisons", true)) {} |
| |
| void SignedCharMisuseCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
| Options.store(Opts, "CharTypdefsToIgnore", CharTypdefsToIgnoreList); |
| Options.store(Opts, "DiagnoseSignedUnsignedCharComparisons", |
| DiagnoseSignedUnsignedCharComparisons); |
| } |
| |
| // Create a matcher for char -> integer cast. |
| BindableMatcher<clang::Stmt> SignedCharMisuseCheck::charCastExpression( |
| bool IsSigned, const Matcher<clang::QualType> &IntegerType, |
| const std::string &CastBindName) const { |
| // We can ignore typedefs which are some kind of integer types |
| // (e.g. typedef char sal_Int8). In this case, we don't need to |
| // worry about the misinterpretation of char values. |
| const auto IntTypedef = qualType( |
| hasDeclaration(typedefDecl(hasAnyListedName(CharTypdefsToIgnoreList)))); |
| |
| auto CharTypeExpr = expr(); |
| if (IsSigned) { |
| CharTypeExpr = expr(hasType( |
| qualType(isAnyCharacter(), isSignedInteger(), unless(IntTypedef)))); |
| } else { |
| CharTypeExpr = expr(hasType(qualType( |
| isAnyCharacter(), unless(isSignedInteger()), unless(IntTypedef)))); |
| } |
| |
| const auto ImplicitCastExpr = |
| implicitCastExpr(hasSourceExpression(CharTypeExpr), |
| hasImplicitDestinationType(IntegerType)) |
| .bind(CastBindName); |
| |
| const auto CStyleCastExpr = cStyleCastExpr(has(ImplicitCastExpr)); |
| const auto StaticCastExpr = cxxStaticCastExpr(has(ImplicitCastExpr)); |
| const auto FunctionalCastExpr = cxxFunctionalCastExpr(has(ImplicitCastExpr)); |
| |
| // We catch any type of casts to an integer. We need to have these cast |
| // expressions explicitly to catch only those casts which are direct children |
| // of the checked expressions. (e.g. assignment, declaration). |
| return traverse(TK_AsIs, expr(anyOf(ImplicitCastExpr, CStyleCastExpr, |
| StaticCastExpr, FunctionalCastExpr))); |
| } |
| |
| void SignedCharMisuseCheck::registerMatchers(MatchFinder *Finder) { |
| const auto IntegerType = |
| qualType(isInteger(), unless(isAnyCharacter()), unless(booleanType())) |
| .bind("integerType"); |
| const auto SignedCharCastExpr = |
| charCastExpression(true, IntegerType, "signedCastExpression"); |
| const auto UnSignedCharCastExpr = |
| charCastExpression(false, IntegerType, "unsignedCastExpression"); |
| |
| // Catch assignments with signed char -> integer conversion. |
| const auto AssignmentOperatorExpr = |
| expr(binaryOperator(hasOperatorName("="), hasLHS(hasType(IntegerType)), |
| hasRHS(SignedCharCastExpr))); |
| |
| Finder->addMatcher(AssignmentOperatorExpr, this); |
| |
| // Catch declarations with signed char -> integer conversion. |
| const auto Declaration = varDecl(isDefinition(), hasType(IntegerType), |
| hasInitializer(SignedCharCastExpr)); |
| |
| Finder->addMatcher(Declaration, this); |
| |
| if (DiagnoseSignedUnsignedCharComparisons) { |
| // Catch signed char/unsigned char comparison. |
| const auto CompareOperator = |
| expr(binaryOperator(hasAnyOperatorName("==", "!="), |
| anyOf(allOf(hasLHS(SignedCharCastExpr), |
| hasRHS(UnSignedCharCastExpr)), |
| allOf(hasLHS(UnSignedCharCastExpr), |
| hasRHS(SignedCharCastExpr))))) |
| .bind("comparison"); |
| |
| Finder->addMatcher(CompareOperator, this); |
| } |
| |
| // Catch array subscripts with signed char -> integer conversion. |
| // Matcher for C arrays. |
| const auto CArraySubscript = |
| arraySubscriptExpr(hasIndex(SignedCharCastExpr)).bind("arraySubscript"); |
| |
| Finder->addMatcher(CArraySubscript, this); |
| |
| // Matcher for std arrays. |
| const auto STDArraySubscript = |
| cxxOperatorCallExpr( |
| hasOverloadedOperatorName("[]"), |
| hasArgument(0, hasType(cxxRecordDecl(hasName("::std::array")))), |
| hasArgument(1, SignedCharCastExpr)) |
| .bind("arraySubscript"); |
| |
| Finder->addMatcher(STDArraySubscript, this); |
| } |
| |
| void SignedCharMisuseCheck::check(const MatchFinder::MatchResult &Result) { |
| const auto *SignedCastExpression = |
| Result.Nodes.getNodeAs<ImplicitCastExpr>("signedCastExpression"); |
| const auto *IntegerType = Result.Nodes.getNodeAs<QualType>("integerType"); |
| assert(SignedCastExpression); |
| assert(IntegerType); |
| |
| // Ignore the match if we know that the signed char's value is not negative. |
| // The potential misinterpretation happens for negative values only. |
| Expr::EvalResult EVResult; |
| if (!SignedCastExpression->isValueDependent() && |
| SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult, |
| *Result.Context)) { |
| llvm::APSInt Value = EVResult.Val.getInt(); |
| if (Value.isNonNegative()) |
| return; |
| } |
| |
| if (const auto *Comparison = Result.Nodes.getNodeAs<Expr>("comparison")) { |
| const auto *UnSignedCastExpression = |
| Result.Nodes.getNodeAs<ImplicitCastExpr>("unsignedCastExpression"); |
| |
| // We can ignore the ASCII value range also for unsigned char. |
| Expr::EvalResult EVResult; |
| if (!UnSignedCastExpression->isValueDependent() && |
| UnSignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult, |
| *Result.Context)) { |
| llvm::APSInt Value = EVResult.Val.getInt(); |
| if (Value <= UnsignedASCIIUpperBound) |
| return; |
| } |
| |
| diag(Comparison->getBeginLoc(), |
| "comparison between 'signed char' and 'unsigned char'"); |
| } else if (Result.Nodes.getNodeAs<Expr>("arraySubscript")) { |
| diag(SignedCastExpression->getBeginLoc(), |
| "'signed char' to %0 conversion in array subscript; " |
| "consider casting to 'unsigned char' first.") |
| << *IntegerType; |
| } else { |
| diag(SignedCastExpression->getBeginLoc(), |
| "'signed char' to %0 conversion; " |
| "consider casting to 'unsigned char' first.") |
| << *IntegerType; |
| } |
| } |
| |
| } // namespace bugprone |
| } // namespace tidy |
| } // namespace clang |