StringIntegerAssignmentCheck.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. //===--- StringIntegerAssignmentCheck.cpp - clang-tidy---------------------===//
  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-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. #include "StringIntegerAssignmentCheck.h"
  9. #include "clang/AST/ASTContext.h"
  10. #include "clang/ASTMatchers/ASTMatchFinder.h"
  11. #include "clang/Lex/Lexer.h"
  12. using namespace clang::ast_matchers;
  13. namespace clang::tidy::bugprone {
  14. void StringIntegerAssignmentCheck::registerMatchers(MatchFinder *Finder) {
  15. Finder->addMatcher(
  16. cxxOperatorCallExpr(
  17. hasAnyOverloadedOperatorName("=", "+="),
  18. callee(cxxMethodDecl(ofClass(classTemplateSpecializationDecl(
  19. hasName("::std::basic_string"),
  20. hasTemplateArgument(0, refersToType(hasCanonicalType(
  21. qualType().bind("type")))))))),
  22. hasArgument(
  23. 1,
  24. ignoringImpCasts(
  25. expr(hasType(isInteger()), unless(hasType(isAnyCharacter())),
  26. // Ignore calls to tolower/toupper (see PR27723).
  27. unless(callExpr(callee(functionDecl(
  28. hasAnyName("tolower", "std::tolower", "toupper",
  29. "std::toupper"))))),
  30. // Do not warn if assigning e.g. `CodePoint` to
  31. // `basic_string<CodePoint>`
  32. unless(hasType(qualType(
  33. hasCanonicalType(equalsBoundNode("type"))))))
  34. .bind("expr"))),
  35. unless(isInTemplateInstantiation())),
  36. this);
  37. }
  38. class CharExpressionDetector {
  39. public:
  40. CharExpressionDetector(QualType CharType, const ASTContext &Ctx)
  41. : CharType(CharType), Ctx(Ctx) {}
  42. bool isLikelyCharExpression(const Expr *E) const {
  43. if (isCharTyped(E))
  44. return true;
  45. if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
  46. const auto *LHS = BinOp->getLHS()->IgnoreParenImpCasts();
  47. const auto *RHS = BinOp->getRHS()->IgnoreParenImpCasts();
  48. // Handle both directions, e.g. `'a' + (i % 26)` and `(i % 26) + 'a'`.
  49. if (BinOp->isAdditiveOp() || BinOp->isBitwiseOp())
  50. return handleBinaryOp(BinOp->getOpcode(), LHS, RHS) ||
  51. handleBinaryOp(BinOp->getOpcode(), RHS, LHS);
  52. // Except in the case of '%'.
  53. if (BinOp->getOpcode() == BO_Rem)
  54. return handleBinaryOp(BinOp->getOpcode(), LHS, RHS);
  55. return false;
  56. }
  57. // Ternary where at least one branch is a likely char expression, e.g.
  58. // i < 265 ? i : ' '
  59. if (const auto *CondOp = dyn_cast<AbstractConditionalOperator>(E))
  60. return isLikelyCharExpression(
  61. CondOp->getFalseExpr()->IgnoreParenImpCasts()) ||
  62. isLikelyCharExpression(
  63. CondOp->getTrueExpr()->IgnoreParenImpCasts());
  64. return false;
  65. }
  66. private:
  67. bool handleBinaryOp(clang::BinaryOperatorKind Opcode, const Expr *const LHS,
  68. const Expr *const RHS) const {
  69. // <char_expr> <op> <char_expr> (c++ integer promotion rules make this an
  70. // int), e.g.
  71. // 'a' + c
  72. if (isCharTyped(LHS) && isCharTyped(RHS))
  73. return true;
  74. // <expr> & <char_valued_constant> or <expr> % <char_valued_constant>, e.g.
  75. // i & 0xff
  76. if ((Opcode == BO_And || Opcode == BO_Rem) && isCharValuedConstant(RHS))
  77. return true;
  78. // <char_expr> | <char_valued_constant>, e.g.
  79. // c | 0x80
  80. if (Opcode == BO_Or && isCharTyped(LHS) && isCharValuedConstant(RHS))
  81. return true;
  82. // <char_constant> + <likely_char_expr>, e.g.
  83. // 'a' + (i % 26)
  84. if (Opcode == BO_Add)
  85. return isCharConstant(LHS) && isLikelyCharExpression(RHS);
  86. return false;
  87. }
  88. // Returns true if `E` is an character constant.
  89. bool isCharConstant(const Expr *E) const {
  90. return isCharTyped(E) && isCharValuedConstant(E);
  91. };
  92. // Returns true if `E` is an integer constant which fits in `CharType`.
  93. bool isCharValuedConstant(const Expr *E) const {
  94. if (E->isInstantiationDependent())
  95. return false;
  96. Expr::EvalResult EvalResult;
  97. if (!E->EvaluateAsInt(EvalResult, Ctx, Expr::SE_AllowSideEffects))
  98. return false;
  99. return EvalResult.Val.getInt().getActiveBits() <= Ctx.getTypeSize(CharType);
  100. };
  101. // Returns true if `E` has the right character type.
  102. bool isCharTyped(const Expr *E) const {
  103. return E->getType().getCanonicalType().getTypePtr() ==
  104. CharType.getTypePtr();
  105. };
  106. const QualType CharType;
  107. const ASTContext &Ctx;
  108. };
  109. void StringIntegerAssignmentCheck::check(
  110. const MatchFinder::MatchResult &Result) {
  111. const auto *Argument = Result.Nodes.getNodeAs<Expr>("expr");
  112. const auto CharType =
  113. Result.Nodes.getNodeAs<QualType>("type")->getCanonicalType();
  114. SourceLocation Loc = Argument->getBeginLoc();
  115. // Try to detect a few common expressions to reduce false positives.
  116. if (CharExpressionDetector(CharType, *Result.Context)
  117. .isLikelyCharExpression(Argument))
  118. return;
  119. auto Diag =
  120. diag(Loc, "an integer is interpreted as a character code when assigning "
  121. "it to a string; if this is intended, cast the integer to the "
  122. "appropriate character type; if you want a string "
  123. "representation, use the appropriate conversion facility");
  124. if (Loc.isMacroID())
  125. return;
  126. bool IsWideCharType = CharType->isWideCharType();
  127. if (!CharType->isCharType() && !IsWideCharType)
  128. return;
  129. bool IsOneDigit = false;
  130. bool IsLiteral = false;
  131. if (const auto *Literal = dyn_cast<IntegerLiteral>(Argument)) {
  132. IsOneDigit = Literal->getValue().getLimitedValue() < 10;
  133. IsLiteral = true;
  134. }
  135. SourceLocation EndLoc = Lexer::getLocForEndOfToken(
  136. Argument->getEndLoc(), 0, *Result.SourceManager, getLangOpts());
  137. if (IsOneDigit) {
  138. Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L'" : "'")
  139. << FixItHint::CreateInsertion(EndLoc, "'");
  140. return;
  141. }
  142. if (IsLiteral) {
  143. Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L\"" : "\"")
  144. << FixItHint::CreateInsertion(EndLoc, "\"");
  145. return;
  146. }
  147. if (getLangOpts().CPlusPlus11) {
  148. Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "std::to_wstring("
  149. : "std::to_string(")
  150. << FixItHint::CreateInsertion(EndLoc, ")");
  151. }
  152. }
  153. } // namespace clang::tidy::bugprone