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