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