blob: 85d99ee76146fdf3ab2ad08249b9c3a5c58cf594 [file] [log] [blame]
//===--- 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