183 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			183 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C++
		
	
	
	
//===--- 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
 |