1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
|
//===--- 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
|