1c13e271aSBhuminjay Soni //===--- UseStdMinMaxCheck.cpp - clang-tidy -------------------------------===// 2c13e271aSBhuminjay Soni // 3c13e271aSBhuminjay Soni // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4c13e271aSBhuminjay Soni // See https://llvm.org/LICENSE.txt for license information. 5c13e271aSBhuminjay Soni // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6c13e271aSBhuminjay Soni // 7c13e271aSBhuminjay Soni //===----------------------------------------------------------------------===// 8c13e271aSBhuminjay Soni 9c13e271aSBhuminjay Soni #include "UseStdMinMaxCheck.h" 10c13e271aSBhuminjay Soni #include "../utils/ASTUtils.h" 11c13e271aSBhuminjay Soni #include "clang/AST/ASTContext.h" 12c13e271aSBhuminjay Soni #include "clang/ASTMatchers/ASTMatchFinder.h" 13c13e271aSBhuminjay Soni #include "clang/Lex/Preprocessor.h" 14c13e271aSBhuminjay Soni 15c13e271aSBhuminjay Soni using namespace clang::ast_matchers; 16c13e271aSBhuminjay Soni 17c13e271aSBhuminjay Soni namespace clang::tidy::readability { 18c13e271aSBhuminjay Soni 19c13e271aSBhuminjay Soni namespace { 20c13e271aSBhuminjay Soni 21c13e271aSBhuminjay Soni // Ignore if statements that are inside macros. 22c13e271aSBhuminjay Soni AST_MATCHER(IfStmt, isIfInMacro) { 23c13e271aSBhuminjay Soni return Node.getIfLoc().isMacroID() || Node.getEndLoc().isMacroID(); 24c13e271aSBhuminjay Soni } 25c13e271aSBhuminjay Soni 26c13e271aSBhuminjay Soni } // namespace 27c13e271aSBhuminjay Soni 28c13e271aSBhuminjay Soni static const llvm::StringRef AlgorithmHeader("<algorithm>"); 29c13e271aSBhuminjay Soni 30c13e271aSBhuminjay Soni static bool minCondition(const BinaryOperator::Opcode Op, const Expr *CondLhs, 31c13e271aSBhuminjay Soni const Expr *CondRhs, const Expr *AssignLhs, 32c13e271aSBhuminjay Soni const Expr *AssignRhs, const ASTContext &Context) { 33c13e271aSBhuminjay Soni if ((Op == BO_LT || Op == BO_LE) && 34c13e271aSBhuminjay Soni (tidy::utils::areStatementsIdentical(CondLhs, AssignRhs, Context) && 35c13e271aSBhuminjay Soni tidy::utils::areStatementsIdentical(CondRhs, AssignLhs, Context))) 36c13e271aSBhuminjay Soni return true; 37c13e271aSBhuminjay Soni 38c13e271aSBhuminjay Soni if ((Op == BO_GT || Op == BO_GE) && 39c13e271aSBhuminjay Soni (tidy::utils::areStatementsIdentical(CondLhs, AssignLhs, Context) && 40c13e271aSBhuminjay Soni tidy::utils::areStatementsIdentical(CondRhs, AssignRhs, Context))) 41c13e271aSBhuminjay Soni return true; 42c13e271aSBhuminjay Soni 43c13e271aSBhuminjay Soni return false; 44c13e271aSBhuminjay Soni } 45c13e271aSBhuminjay Soni 46c13e271aSBhuminjay Soni static bool maxCondition(const BinaryOperator::Opcode Op, const Expr *CondLhs, 47c13e271aSBhuminjay Soni const Expr *CondRhs, const Expr *AssignLhs, 48c13e271aSBhuminjay Soni const Expr *AssignRhs, const ASTContext &Context) { 49c13e271aSBhuminjay Soni if ((Op == BO_LT || Op == BO_LE) && 50c13e271aSBhuminjay Soni (tidy::utils::areStatementsIdentical(CondLhs, AssignLhs, Context) && 51c13e271aSBhuminjay Soni tidy::utils::areStatementsIdentical(CondRhs, AssignRhs, Context))) 52c13e271aSBhuminjay Soni return true; 53c13e271aSBhuminjay Soni 54c13e271aSBhuminjay Soni if ((Op == BO_GT || Op == BO_GE) && 55c13e271aSBhuminjay Soni (tidy::utils::areStatementsIdentical(CondLhs, AssignRhs, Context) && 56c13e271aSBhuminjay Soni tidy::utils::areStatementsIdentical(CondRhs, AssignLhs, Context))) 57c13e271aSBhuminjay Soni return true; 58c13e271aSBhuminjay Soni 59c13e271aSBhuminjay Soni return false; 60c13e271aSBhuminjay Soni } 61c13e271aSBhuminjay Soni 62504f6ce0SCongcong Cai static QualType getNonTemplateAlias(QualType QT) { 63c13e271aSBhuminjay Soni while (true) { 64c13e271aSBhuminjay Soni // cast to a TypedefType 65c13e271aSBhuminjay Soni if (const TypedefType *TT = dyn_cast<TypedefType>(QT)) { 66c13e271aSBhuminjay Soni // check if the typedef is a template and if it is dependent 67c13e271aSBhuminjay Soni if (!TT->getDecl()->getDescribedTemplate() && 68c13e271aSBhuminjay Soni !TT->getDecl()->getDeclContext()->isDependentContext()) 69c13e271aSBhuminjay Soni return QT; 70c13e271aSBhuminjay Soni QT = TT->getDecl()->getUnderlyingType(); 71c13e271aSBhuminjay Soni } 72c13e271aSBhuminjay Soni // cast to elaborated type 73c13e271aSBhuminjay Soni else if (const ElaboratedType *ET = dyn_cast<ElaboratedType>(QT)) { 74c13e271aSBhuminjay Soni QT = ET->getNamedType(); 75c13e271aSBhuminjay Soni } else { 76c13e271aSBhuminjay Soni break; 77c13e271aSBhuminjay Soni } 78c13e271aSBhuminjay Soni } 79c13e271aSBhuminjay Soni return QT; 80c13e271aSBhuminjay Soni } 81c13e271aSBhuminjay Soni 82*32bcd41aSCongcong Cai static QualType getReplacementCastType(const Expr *CondLhs, const Expr *CondRhs, 83*32bcd41aSCongcong Cai QualType ComparedType) { 84*32bcd41aSCongcong Cai QualType LhsType = CondLhs->getType(); 85*32bcd41aSCongcong Cai QualType RhsType = CondRhs->getType(); 86*32bcd41aSCongcong Cai QualType LhsCanonicalType = 87*32bcd41aSCongcong Cai LhsType.getCanonicalType().getNonReferenceType().getUnqualifiedType(); 88*32bcd41aSCongcong Cai QualType RhsCanonicalType = 89*32bcd41aSCongcong Cai RhsType.getCanonicalType().getNonReferenceType().getUnqualifiedType(); 90*32bcd41aSCongcong Cai QualType GlobalImplicitCastType; 91*32bcd41aSCongcong Cai if (LhsCanonicalType != RhsCanonicalType) { 92*32bcd41aSCongcong Cai if (llvm::isa<IntegerLiteral>(CondRhs)) { 93*32bcd41aSCongcong Cai GlobalImplicitCastType = getNonTemplateAlias(LhsType); 94*32bcd41aSCongcong Cai } else if (llvm::isa<IntegerLiteral>(CondLhs)) { 95*32bcd41aSCongcong Cai GlobalImplicitCastType = getNonTemplateAlias(RhsType); 96*32bcd41aSCongcong Cai } else { 97*32bcd41aSCongcong Cai GlobalImplicitCastType = getNonTemplateAlias(ComparedType); 98*32bcd41aSCongcong Cai } 99*32bcd41aSCongcong Cai } 100*32bcd41aSCongcong Cai return GlobalImplicitCastType; 101*32bcd41aSCongcong Cai } 102*32bcd41aSCongcong Cai 103c13e271aSBhuminjay Soni static std::string createReplacement(const Expr *CondLhs, const Expr *CondRhs, 104c13e271aSBhuminjay Soni const Expr *AssignLhs, 105c13e271aSBhuminjay Soni const SourceManager &Source, 106c13e271aSBhuminjay Soni const LangOptions &LO, 107c13e271aSBhuminjay Soni StringRef FunctionName, 108c13e271aSBhuminjay Soni const BinaryOperator *BO) { 109c13e271aSBhuminjay Soni const llvm::StringRef CondLhsStr = Lexer::getSourceText( 110c13e271aSBhuminjay Soni Source.getExpansionRange(CondLhs->getSourceRange()), Source, LO); 111c13e271aSBhuminjay Soni const llvm::StringRef CondRhsStr = Lexer::getSourceText( 112c13e271aSBhuminjay Soni Source.getExpansionRange(CondRhs->getSourceRange()), Source, LO); 113c13e271aSBhuminjay Soni const llvm::StringRef AssignLhsStr = Lexer::getSourceText( 114c13e271aSBhuminjay Soni Source.getExpansionRange(AssignLhs->getSourceRange()), Source, LO); 115c13e271aSBhuminjay Soni 116*32bcd41aSCongcong Cai QualType GlobalImplicitCastType = 117*32bcd41aSCongcong Cai getReplacementCastType(CondLhs, CondRhs, BO->getLHS()->getType()); 118c13e271aSBhuminjay Soni 119c13e271aSBhuminjay Soni return (AssignLhsStr + " = " + FunctionName + 120c13e271aSBhuminjay Soni (!GlobalImplicitCastType.isNull() 121c13e271aSBhuminjay Soni ? "<" + GlobalImplicitCastType.getAsString() + ">(" 122c13e271aSBhuminjay Soni : "(") + 123c13e271aSBhuminjay Soni CondLhsStr + ", " + CondRhsStr + ");") 124c13e271aSBhuminjay Soni .str(); 125c13e271aSBhuminjay Soni } 126c13e271aSBhuminjay Soni 127c13e271aSBhuminjay Soni UseStdMinMaxCheck::UseStdMinMaxCheck(StringRef Name, ClangTidyContext *Context) 128c13e271aSBhuminjay Soni : ClangTidyCheck(Name, Context), 129c13e271aSBhuminjay Soni IncludeInserter(Options.getLocalOrGlobal("IncludeStyle", 130c13e271aSBhuminjay Soni utils::IncludeSorter::IS_LLVM), 131c13e271aSBhuminjay Soni areDiagsSelfContained()) {} 132c13e271aSBhuminjay Soni 133c13e271aSBhuminjay Soni void UseStdMinMaxCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { 134c13e271aSBhuminjay Soni Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle()); 135c13e271aSBhuminjay Soni } 136c13e271aSBhuminjay Soni 137c13e271aSBhuminjay Soni void UseStdMinMaxCheck::registerMatchers(MatchFinder *Finder) { 138c13e271aSBhuminjay Soni auto AssignOperator = 139c13e271aSBhuminjay Soni binaryOperator(hasOperatorName("="), 140c13e271aSBhuminjay Soni hasLHS(expr(unless(isTypeDependent())).bind("AssignLhs")), 141c13e271aSBhuminjay Soni hasRHS(expr(unless(isTypeDependent())).bind("AssignRhs"))); 142c13e271aSBhuminjay Soni auto BinaryOperator = 143c13e271aSBhuminjay Soni binaryOperator(hasAnyOperatorName("<", ">", "<=", ">="), 144c13e271aSBhuminjay Soni hasLHS(expr(unless(isTypeDependent())).bind("CondLhs")), 145c13e271aSBhuminjay Soni hasRHS(expr(unless(isTypeDependent())).bind("CondRhs"))) 146c13e271aSBhuminjay Soni .bind("binaryOp"); 147c13e271aSBhuminjay Soni Finder->addMatcher( 148c13e271aSBhuminjay Soni ifStmt(stmt().bind("if"), unless(isIfInMacro()), 149c13e271aSBhuminjay Soni unless(hasElse(stmt())), // Ensure `if` has no `else` 150c13e271aSBhuminjay Soni hasCondition(BinaryOperator), 151c13e271aSBhuminjay Soni hasThen( 152c13e271aSBhuminjay Soni anyOf(stmt(AssignOperator), 153c13e271aSBhuminjay Soni compoundStmt(statementCountIs(1), has(AssignOperator)))), 154c13e271aSBhuminjay Soni hasParent(stmt(unless(ifStmt(hasElse( 155c13e271aSBhuminjay Soni equalsBoundNode("if"))))))), // Ensure `if` has no `else if` 156c13e271aSBhuminjay Soni this); 157c13e271aSBhuminjay Soni } 158c13e271aSBhuminjay Soni 159c13e271aSBhuminjay Soni void UseStdMinMaxCheck::registerPPCallbacks(const SourceManager &SM, 160c13e271aSBhuminjay Soni Preprocessor *PP, 161c13e271aSBhuminjay Soni Preprocessor *ModuleExpanderPP) { 162c13e271aSBhuminjay Soni IncludeInserter.registerPreprocessor(PP); 163c13e271aSBhuminjay Soni } 164c13e271aSBhuminjay Soni 165c13e271aSBhuminjay Soni void UseStdMinMaxCheck::check(const MatchFinder::MatchResult &Result) { 166c13e271aSBhuminjay Soni const auto *If = Result.Nodes.getNodeAs<IfStmt>("if"); 167c13e271aSBhuminjay Soni const clang::LangOptions &LO = Result.Context->getLangOpts(); 168c13e271aSBhuminjay Soni const auto *CondLhs = Result.Nodes.getNodeAs<Expr>("CondLhs"); 169c13e271aSBhuminjay Soni const auto *CondRhs = Result.Nodes.getNodeAs<Expr>("CondRhs"); 170c13e271aSBhuminjay Soni const auto *AssignLhs = Result.Nodes.getNodeAs<Expr>("AssignLhs"); 171c13e271aSBhuminjay Soni const auto *AssignRhs = Result.Nodes.getNodeAs<Expr>("AssignRhs"); 172c13e271aSBhuminjay Soni const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("binaryOp"); 173c13e271aSBhuminjay Soni const clang::BinaryOperatorKind BinaryOpcode = BinaryOp->getOpcode(); 174c13e271aSBhuminjay Soni const SourceLocation IfLocation = If->getIfLoc(); 175c13e271aSBhuminjay Soni const SourceLocation ThenLocation = If->getEndLoc(); 176c13e271aSBhuminjay Soni 177c13e271aSBhuminjay Soni auto ReplaceAndDiagnose = [&](const llvm::StringRef FunctionName) { 178c13e271aSBhuminjay Soni const SourceManager &Source = *Result.SourceManager; 179c13e271aSBhuminjay Soni diag(IfLocation, "use `%0` instead of `%1`") 180c13e271aSBhuminjay Soni << FunctionName << BinaryOp->getOpcodeStr() 181c13e271aSBhuminjay Soni << FixItHint::CreateReplacement( 182c13e271aSBhuminjay Soni SourceRange(IfLocation, Lexer::getLocForEndOfToken( 183c13e271aSBhuminjay Soni ThenLocation, 0, Source, LO)), 184c13e271aSBhuminjay Soni createReplacement(CondLhs, CondRhs, AssignLhs, Source, LO, 185c13e271aSBhuminjay Soni FunctionName, BinaryOp)) 186c13e271aSBhuminjay Soni << IncludeInserter.createIncludeInsertion( 187c13e271aSBhuminjay Soni Source.getFileID(If->getBeginLoc()), AlgorithmHeader); 188c13e271aSBhuminjay Soni }; 189c13e271aSBhuminjay Soni 190c13e271aSBhuminjay Soni if (minCondition(BinaryOpcode, CondLhs, CondRhs, AssignLhs, AssignRhs, 191c13e271aSBhuminjay Soni (*Result.Context))) { 192c13e271aSBhuminjay Soni ReplaceAndDiagnose("std::min"); 193c13e271aSBhuminjay Soni } else if (maxCondition(BinaryOpcode, CondLhs, CondRhs, AssignLhs, AssignRhs, 194c13e271aSBhuminjay Soni (*Result.Context))) { 195c13e271aSBhuminjay Soni ReplaceAndDiagnose("std::max"); 196c13e271aSBhuminjay Soni } 197c13e271aSBhuminjay Soni } 198c13e271aSBhuminjay Soni 199c13e271aSBhuminjay Soni } // namespace clang::tidy::readability 200