| //===--- StringIntegerAssignmentCheck.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 "StringIntegerAssignmentCheck.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/Lex/Lexer.h" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace bugprone { |
| |
| void StringIntegerAssignmentCheck::registerMatchers(MatchFinder *Finder) { |
| Finder->addMatcher( |
| cxxOperatorCallExpr( |
| hasAnyOverloadedOperatorName("=", "+="), |
| callee(cxxMethodDecl(ofClass(classTemplateSpecializationDecl( |
| hasName("::std::basic_string"), |
| hasTemplateArgument(0, refersToType(hasCanonicalType( |
| qualType().bind("type")))))))), |
| hasArgument( |
| 1, |
| ignoringImpCasts( |
| expr(hasType(isInteger()), unless(hasType(isAnyCharacter())), |
| // Ignore calls to tolower/toupper (see PR27723). |
| unless(callExpr(callee(functionDecl( |
| hasAnyName("tolower", "std::tolower", "toupper", |
| "std::toupper"))))), |
| // Do not warn if assigning e.g. `CodePoint` to |
| // `basic_string<CodePoint>` |
| unless(hasType(qualType( |
| hasCanonicalType(equalsBoundNode("type")))))) |
| .bind("expr"))), |
| unless(isInTemplateInstantiation())), |
| this); |
| } |
| |
| class CharExpressionDetector { |
| public: |
| CharExpressionDetector(QualType CharType, const ASTContext &Ctx) |
| : CharType(CharType), Ctx(Ctx) {} |
| |
| bool isLikelyCharExpression(const Expr *E) const { |
| if (isCharTyped(E)) |
| return true; |
| |
| if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) { |
| const auto *LHS = BinOp->getLHS()->IgnoreParenImpCasts(); |
| const auto *RHS = BinOp->getRHS()->IgnoreParenImpCasts(); |
| // Handle both directions, e.g. `'a' + (i % 26)` and `(i % 26) + 'a'`. |
| if (BinOp->isAdditiveOp() || BinOp->isBitwiseOp()) |
| return handleBinaryOp(BinOp->getOpcode(), LHS, RHS) || |
| handleBinaryOp(BinOp->getOpcode(), RHS, LHS); |
| // Except in the case of '%'. |
| if (BinOp->getOpcode() == BO_Rem) |
| return handleBinaryOp(BinOp->getOpcode(), LHS, RHS); |
| return false; |
| } |
| |
| // Ternary where at least one branch is a likely char expression, e.g. |
| // i < 265 ? i : ' ' |
| if (const auto *CondOp = dyn_cast<AbstractConditionalOperator>(E)) |
| return isLikelyCharExpression( |
| CondOp->getFalseExpr()->IgnoreParenImpCasts()) || |
| isLikelyCharExpression( |
| CondOp->getTrueExpr()->IgnoreParenImpCasts()); |
| return false; |
| } |
| |
| private: |
| bool handleBinaryOp(clang::BinaryOperatorKind Opcode, const Expr *const LHS, |
| const Expr *const RHS) const { |
| // <char_expr> <op> <char_expr> (c++ integer promotion rules make this an |
| // int), e.g. |
| // 'a' + c |
| if (isCharTyped(LHS) && isCharTyped(RHS)) |
| return true; |
| |
| // <expr> & <char_valued_constant> or <expr> % <char_valued_constant>, e.g. |
| // i & 0xff |
| if ((Opcode == BO_And || Opcode == BO_Rem) && isCharValuedConstant(RHS)) |
| return true; |
| |
| // <char_expr> | <char_valued_constant>, e.g. |
| // c | 0x80 |
| if (Opcode == BO_Or && isCharTyped(LHS) && isCharValuedConstant(RHS)) |
| return true; |
| |
| // <char_constant> + <likely_char_expr>, e.g. |
| // 'a' + (i % 26) |
| if (Opcode == BO_Add) |
| return isCharConstant(LHS) && isLikelyCharExpression(RHS); |
| |
| return false; |
| } |
| |
| // Returns true if `E` is an character constant. |
| bool isCharConstant(const Expr *E) const { |
| return isCharTyped(E) && isCharValuedConstant(E); |
| }; |
| |
| // Returns true if `E` is an integer constant which fits in `CharType`. |
| bool isCharValuedConstant(const Expr *E) const { |
| if (E->isInstantiationDependent()) |
| return false; |
| Expr::EvalResult EvalResult; |
| if (!E->EvaluateAsInt(EvalResult, Ctx, Expr::SE_AllowSideEffects)) |
| return false; |
| return EvalResult.Val.getInt().getActiveBits() <= Ctx.getTypeSize(CharType); |
| }; |
| |
| // Returns true if `E` has the right character type. |
| bool isCharTyped(const Expr *E) const { |
| return E->getType().getCanonicalType().getTypePtr() == |
| CharType.getTypePtr(); |
| }; |
| |
| const QualType CharType; |
| const ASTContext &Ctx; |
| }; |
| |
| void StringIntegerAssignmentCheck::check( |
| const MatchFinder::MatchResult &Result) { |
| const auto *Argument = Result.Nodes.getNodeAs<Expr>("expr"); |
| const auto CharType = |
| Result.Nodes.getNodeAs<QualType>("type")->getCanonicalType(); |
| SourceLocation Loc = Argument->getBeginLoc(); |
| |
| // Try to detect a few common expressions to reduce false positives. |
| if (CharExpressionDetector(CharType, *Result.Context) |
| .isLikelyCharExpression(Argument)) |
| return; |
| |
| auto Diag = |
| diag(Loc, "an integer is interpreted as a character code when assigning " |
| "it to a string; if this is intended, cast the integer to the " |
| "appropriate character type; if you want a string " |
| "representation, use the appropriate conversion facility"); |
| |
| if (Loc.isMacroID()) |
| return; |
| |
| bool IsWideCharType = CharType->isWideCharType(); |
| if (!CharType->isCharType() && !IsWideCharType) |
| return; |
| bool IsOneDigit = false; |
| bool IsLiteral = false; |
| if (const auto *Literal = dyn_cast<IntegerLiteral>(Argument)) { |
| IsOneDigit = Literal->getValue().getLimitedValue() < 10; |
| IsLiteral = true; |
| } |
| |
| SourceLocation EndLoc = Lexer::getLocForEndOfToken( |
| Argument->getEndLoc(), 0, *Result.SourceManager, getLangOpts()); |
| if (IsOneDigit) { |
| Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L'" : "'") |
| << FixItHint::CreateInsertion(EndLoc, "'"); |
| return; |
| } |
| if (IsLiteral) { |
| Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L\"" : "\"") |
| << FixItHint::CreateInsertion(EndLoc, "\""); |
| return; |
| } |
| |
| if (getLangOpts().CPlusPlus11) { |
| Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "std::to_wstring(" |
| : "std::to_string(") |
| << FixItHint::CreateInsertion(EndLoc, ")"); |
| } |
| } |
| |
| } // namespace bugprone |
| } // namespace tidy |
| } // namespace clang |