blob: eeba5cce80da5dcce64505f8f6eee3c409990acc [file] [log] [blame]
//===--- UseIntegerSignComparisonCheck.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 "UseIntegerSignComparisonCheck.h"
#include "clang/AST/Expr.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
using namespace clang::ast_matchers;
using namespace clang::ast_matchers::internal;
namespace clang::tidy::modernize {
/// Find if the passed type is the actual "char" type,
/// not applicable to explicit "signed char" or "unsigned char" types.
static bool isActualCharType(const clang::QualType &Ty) {
using namespace clang;
const Type *DesugaredType = Ty->getUnqualifiedDesugaredType();
if (const auto *BT = llvm::dyn_cast<BuiltinType>(DesugaredType))
return (BT->getKind() == BuiltinType::Char_U ||
BT->getKind() == BuiltinType::Char_S);
return false;
}
namespace {
AST_MATCHER(clang::QualType, isActualChar) {
return clang::tidy::modernize::isActualCharType(Node);
}
} // namespace
static BindableMatcher<clang::Stmt>
intCastExpression(bool IsSigned,
const std::string &CastBindName = std::string()) {
// std::cmp_{} functions trigger a compile-time error if either LHS or RHS
// is a non-integer type, char, enum or bool
// (unsigned char/ signed char are Ok and can be used).
auto IntTypeExpr = expr(hasType(hasCanonicalType(qualType(
isInteger(), IsSigned ? isSignedInteger() : isUnsignedInteger(),
unless(isActualChar()), unless(booleanType()), unless(enumType())))));
const auto ImplicitCastExpr =
CastBindName.empty() ? implicitCastExpr(hasSourceExpression(IntTypeExpr))
: implicitCastExpr(hasSourceExpression(IntTypeExpr))
.bind(CastBindName);
const auto CStyleCastExpr = cStyleCastExpr(has(ImplicitCastExpr));
const auto StaticCastExpr = cxxStaticCastExpr(has(ImplicitCastExpr));
const auto FunctionalCastExpr = cxxFunctionalCastExpr(has(ImplicitCastExpr));
return expr(anyOf(ImplicitCastExpr, CStyleCastExpr, StaticCastExpr,
FunctionalCastExpr));
}
static StringRef parseOpCode(BinaryOperator::Opcode Code) {
switch (Code) {
case BO_LT:
return "cmp_less";
case BO_GT:
return "cmp_greater";
case BO_LE:
return "cmp_less_equal";
case BO_GE:
return "cmp_greater_equal";
case BO_EQ:
return "cmp_equal";
case BO_NE:
return "cmp_not_equal";
default:
return "";
}
}
UseIntegerSignComparisonCheck::UseIntegerSignComparisonCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
utils::IncludeSorter::IS_LLVM),
areDiagsSelfContained()),
EnableQtSupport(Options.get("EnableQtSupport", false)) {}
void UseIntegerSignComparisonCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
Options.store(Opts, "EnableQtSupport", EnableQtSupport);
}
void UseIntegerSignComparisonCheck::registerMatchers(MatchFinder *Finder) {
const auto SignedIntCastExpr = intCastExpression(true, "sIntCastExpression");
const auto UnSignedIntCastExpr = intCastExpression(false);
// Flag all operators "==", "<=", ">=", "<", ">", "!="
// that are used between signed/unsigned
const auto CompareOperator =
binaryOperator(hasAnyOperatorName("==", "<=", ">=", "<", ">", "!="),
hasOperands(SignedIntCastExpr, UnSignedIntCastExpr),
unless(isInTemplateInstantiation()))
.bind("intComparison");
Finder->addMatcher(CompareOperator, this);
}
void UseIntegerSignComparisonCheck::registerPPCallbacks(
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
IncludeInserter.registerPreprocessor(PP);
}
void UseIntegerSignComparisonCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *SignedCastExpression =
Result.Nodes.getNodeAs<ImplicitCastExpr>("sIntCastExpression");
assert(SignedCastExpression);
// Ignore the match if we know that the signed int value is not negative.
Expr::EvalResult EVResult;
if (!SignedCastExpression->isValueDependent() &&
SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,
*Result.Context)) {
const llvm::APSInt SValue = EVResult.Val.getInt();
if (SValue.isNonNegative())
return;
}
const auto *BinaryOp =
Result.Nodes.getNodeAs<BinaryOperator>("intComparison");
if (BinaryOp == nullptr)
return;
const BinaryOperator::Opcode OpCode = BinaryOp->getOpcode();
const Expr *LHS = BinaryOp->getLHS()->IgnoreImpCasts();
const Expr *RHS = BinaryOp->getRHS()->IgnoreImpCasts();
if (LHS == nullptr || RHS == nullptr)
return;
const Expr *SubExprLHS = nullptr;
const Expr *SubExprRHS = nullptr;
SourceRange R1 = SourceRange(LHS->getBeginLoc());
SourceRange R2 = SourceRange(BinaryOp->getOperatorLoc());
SourceRange R3 = SourceRange(Lexer::getLocForEndOfToken(
RHS->getEndLoc(), 0, *Result.SourceManager, getLangOpts()));
if (const auto *LHSCast = llvm::dyn_cast<ExplicitCastExpr>(LHS)) {
SubExprLHS = LHSCast->getSubExpr();
R1 = SourceRange(LHS->getBeginLoc(),
SubExprLHS->getBeginLoc().getLocWithOffset(-1));
R2.setBegin(Lexer::getLocForEndOfToken(
SubExprLHS->getEndLoc(), 0, *Result.SourceManager, getLangOpts()));
}
if (const auto *RHSCast = llvm::dyn_cast<ExplicitCastExpr>(RHS)) {
SubExprRHS = RHSCast->getSubExpr();
R2.setEnd(SubExprRHS->getBeginLoc().getLocWithOffset(-1));
}
DiagnosticBuilder Diag =
diag(BinaryOp->getBeginLoc(),
"comparison between 'signed' and 'unsigned' integers");
std::string CmpNamespace;
llvm::StringRef CmpHeader;
if (getLangOpts().CPlusPlus20) {
CmpHeader = "<utility>";
CmpNamespace = llvm::Twine("std::" + parseOpCode(OpCode)).str();
} else if (getLangOpts().CPlusPlus17 && EnableQtSupport) {
CmpHeader = "<QtCore/q20utility.h>";
CmpNamespace = llvm::Twine("q20::" + parseOpCode(OpCode)).str();
}
// Prefer modernize-use-integer-sign-comparison when C++20 is available!
Diag << FixItHint::CreateReplacement(
CharSourceRange(R1, SubExprLHS != nullptr),
llvm::Twine(CmpNamespace + "(").str());
Diag << FixItHint::CreateReplacement(R2, ",");
Diag << FixItHint::CreateReplacement(CharSourceRange::getCharRange(R3), ")");
// If there is no include for cmp_{*} functions, we'll add it.
Diag << IncludeInserter.createIncludeInsertion(
Result.SourceManager->getFileID(BinaryOp->getBeginLoc()), CmpHeader);
}
} // namespace clang::tidy::modernize