123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- //===--- 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::tidy::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 clang::tidy::bugprone
|