diff options
author | thegeorg <thegeorg@yandex-team.com> | 2024-03-13 13:58:24 +0300 |
---|---|---|
committer | thegeorg <thegeorg@yandex-team.com> | 2024-03-13 14:11:53 +0300 |
commit | 11a895b7e15d1c5a1f52706396b82e3f9db953cb (patch) | |
tree | fabc6d883b0f946151f61ae7865cee9f529a1fdd /contrib/libs/clang16/tools/extra/clang-tidy/readability | |
parent | 9685917341315774aad5733b1793b1e533a88bbb (diff) | |
download | ydb-11a895b7e15d1c5a1f52706396b82e3f9db953cb.tar.gz |
Export clang-format16 via ydblib project
6e6be3a95868fde888d801b7590af4044049563f
Diffstat (limited to 'contrib/libs/clang16/tools/extra/clang-tidy/readability')
88 files changed, 12493 insertions, 0 deletions
diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/AvoidConstParamsInDecls.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/AvoidConstParamsInDecls.cpp new file mode 100644 index 0000000000..6906717dd4 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/AvoidConstParamsInDecls.cpp @@ -0,0 +1,90 @@ +//===--- AvoidConstParamsInDecls.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 "AvoidConstParamsInDecls.h" +#include "../utils/LexerUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { +namespace { + +SourceRange getTypeRange(const ParmVarDecl &Param) { + return SourceRange(Param.getBeginLoc(), + Param.getLocation().getLocWithOffset(-1)); +} + +} // namespace + +void AvoidConstParamsInDecls::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreMacros", IgnoreMacros); +} + +void AvoidConstParamsInDecls::registerMatchers(MatchFinder *Finder) { + const auto ConstParamDecl = + parmVarDecl(hasType(qualType(isConstQualified()))).bind("param"); + Finder->addMatcher( + functionDecl(unless(isDefinition()), + has(typeLoc(forEach(ConstParamDecl)))) + .bind("func"), + this); +} + +void AvoidConstParamsInDecls::check(const MatchFinder::MatchResult &Result) { + const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func"); + const auto *Param = Result.Nodes.getNodeAs<ParmVarDecl>("param"); + + if (!Param->getType().isLocalConstQualified()) + return; + + if (IgnoreMacros && + (Param->getBeginLoc().isMacroID() || Param->getEndLoc().isMacroID())) { + // Suppress the check if macros are involved. + return; + } + + auto Diag = diag(Param->getBeginLoc(), + "parameter %0 is const-qualified in the function " + "declaration; const-qualification of parameters only has an " + "effect in function definitions"); + if (Param->getName().empty()) { + for (unsigned int I = 0; I < Func->getNumParams(); ++I) { + if (Param == Func->getParamDecl(I)) { + Diag << (I + 1); + break; + } + } + } else { + Diag << Param; + } + + if (Param->getBeginLoc().isMacroID() != Param->getEndLoc().isMacroID()) { + // Do not offer a suggestion if the part of the variable declaration comes + // from a macro. + return; + } + + CharSourceRange FileRange = Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(getTypeRange(*Param)), + *Result.SourceManager, getLangOpts()); + + if (!FileRange.isValid()) + return; + + auto Tok = tidy::utils::lexer::getQualifyingToken( + tok::kw_const, FileRange, *Result.Context, *Result.SourceManager); + if (!Tok) + return; + Diag << FixItHint::CreateRemoval( + CharSourceRange::getTokenRange(Tok->getLocation(), Tok->getLocation())); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/AvoidConstParamsInDecls.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/AvoidConstParamsInDecls.h new file mode 100644 index 0000000000..57bac2b39b --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/AvoidConstParamsInDecls.h @@ -0,0 +1,37 @@ +//===--- AvoidConstParamsInDecls.h - 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOID_CONST_PARAMS_IN_DECLS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOID_CONST_PARAMS_IN_DECLS_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +// Detect function declarations that have const value parameters and discourage +// them. +class AvoidConstParamsInDecls : public ClangTidyCheck { +public: + AvoidConstParamsInDecls(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + const bool IgnoreMacros; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_AVOID_CONST_PARAMS_IN_DECLS_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/BracesAroundStatementsCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/BracesAroundStatementsCheck.cpp new file mode 100644 index 0000000000..74bb073848 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/BracesAroundStatementsCheck.cpp @@ -0,0 +1,270 @@ +//===--- BracesAroundStatementsCheck.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 "BracesAroundStatementsCheck.h" +#include "../utils/LexerUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +static tok::TokenKind getTokenKind(SourceLocation Loc, const SourceManager &SM, + const ASTContext *Context) { + Token Tok; + SourceLocation Beginning = + Lexer::GetBeginningOfToken(Loc, SM, Context->getLangOpts()); + const bool Invalid = + Lexer::getRawToken(Beginning, Tok, SM, Context->getLangOpts()); + assert(!Invalid && "Expected a valid token."); + + if (Invalid) + return tok::NUM_TOKENS; + + return Tok.getKind(); +} + +static SourceLocation +forwardSkipWhitespaceAndComments(SourceLocation Loc, const SourceManager &SM, + const ASTContext *Context) { + assert(Loc.isValid()); + for (;;) { + while (isWhitespace(*SM.getCharacterData(Loc))) + Loc = Loc.getLocWithOffset(1); + + tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); + if (TokKind != tok::comment) + return Loc; + + // Fast-forward current token. + Loc = Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); + } +} + +static SourceLocation findEndLocation(const Stmt &S, const SourceManager &SM, + const ASTContext *Context) { + SourceLocation Loc = + utils::lexer::getUnifiedEndLoc(S, SM, Context->getLangOpts()); + if (!Loc.isValid()) + return Loc; + + // Start searching right after S. + Loc = Loc.getLocWithOffset(1); + + for (;;) { + assert(Loc.isValid()); + while (isHorizontalWhitespace(*SM.getCharacterData(Loc))) { + Loc = Loc.getLocWithOffset(1); + } + + if (isVerticalWhitespace(*SM.getCharacterData(Loc))) { + // EOL, insert brace before. + break; + } + tok::TokenKind TokKind = getTokenKind(Loc, SM, Context); + if (TokKind != tok::comment) { + // Non-comment token, insert brace before. + break; + } + + SourceLocation TokEndLoc = + Lexer::getLocForEndOfToken(Loc, 0, SM, Context->getLangOpts()); + SourceRange TokRange(Loc, TokEndLoc); + StringRef Comment = Lexer::getSourceText( + CharSourceRange::getTokenRange(TokRange), SM, Context->getLangOpts()); + if (Comment.startswith("/*") && Comment.contains('\n')) { + // Multi-line block comment, insert brace before. + break; + } + // else: Trailing comment, insert brace after the newline. + + // Fast-forward current token. + Loc = TokEndLoc; + } + return Loc; +} + +BracesAroundStatementsCheck::BracesAroundStatementsCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + // Always add braces by default. + ShortStatementLines(Options.get("ShortStatementLines", 0U)) {} + +void BracesAroundStatementsCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ShortStatementLines", ShortStatementLines); +} + +void BracesAroundStatementsCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(ifStmt().bind("if"), this); + Finder->addMatcher(whileStmt().bind("while"), this); + Finder->addMatcher(doStmt().bind("do"), this); + Finder->addMatcher(forStmt().bind("for"), this); + Finder->addMatcher(cxxForRangeStmt().bind("for-range"), this); +} + +void BracesAroundStatementsCheck::check( + const MatchFinder::MatchResult &Result) { + const SourceManager &SM = *Result.SourceManager; + const ASTContext *Context = Result.Context; + + // Get location of closing parenthesis or 'do' to insert opening brace. + if (const auto *S = Result.Nodes.getNodeAs<ForStmt>("for")) { + checkStmt(Result, S->getBody(), S->getRParenLoc()); + } else if (const auto *S = + Result.Nodes.getNodeAs<CXXForRangeStmt>("for-range")) { + checkStmt(Result, S->getBody(), S->getRParenLoc()); + } else if (const auto *S = Result.Nodes.getNodeAs<DoStmt>("do")) { + checkStmt(Result, S->getBody(), S->getDoLoc(), S->getWhileLoc()); + } else if (const auto *S = Result.Nodes.getNodeAs<WhileStmt>("while")) { + SourceLocation StartLoc = findRParenLoc(S, SM, Context); + if (StartLoc.isInvalid()) + return; + checkStmt(Result, S->getBody(), StartLoc); + } else if (const auto *S = Result.Nodes.getNodeAs<IfStmt>("if")) { + // "if consteval" always has braces. + if (S->isConsteval()) + return; + + SourceLocation StartLoc = findRParenLoc(S, SM, Context); + if (StartLoc.isInvalid()) + return; + if (ForceBracesStmts.erase(S)) + ForceBracesStmts.insert(S->getThen()); + bool BracedIf = checkStmt(Result, S->getThen(), StartLoc, S->getElseLoc()); + const Stmt *Else = S->getElse(); + if (Else && BracedIf) + ForceBracesStmts.insert(Else); + if (Else && !isa<IfStmt>(Else)) { + // Omit 'else if' statements here, they will be handled directly. + checkStmt(Result, Else, S->getElseLoc()); + } + } else { + llvm_unreachable("Invalid match"); + } +} + +/// Find location of right parenthesis closing condition. +template <typename IfOrWhileStmt> +SourceLocation +BracesAroundStatementsCheck::findRParenLoc(const IfOrWhileStmt *S, + const SourceManager &SM, + const ASTContext *Context) { + // Skip macros. + if (S->getBeginLoc().isMacroID()) + return SourceLocation(); + + SourceLocation CondEndLoc = S->getCond()->getEndLoc(); + if (const DeclStmt *CondVar = S->getConditionVariableDeclStmt()) + CondEndLoc = CondVar->getEndLoc(); + + if (!CondEndLoc.isValid()) { + return SourceLocation(); + } + + SourceLocation PastCondEndLoc = + Lexer::getLocForEndOfToken(CondEndLoc, 0, SM, Context->getLangOpts()); + if (PastCondEndLoc.isInvalid()) + return SourceLocation(); + SourceLocation RParenLoc = + forwardSkipWhitespaceAndComments(PastCondEndLoc, SM, Context); + if (RParenLoc.isInvalid()) + return SourceLocation(); + tok::TokenKind TokKind = getTokenKind(RParenLoc, SM, Context); + if (TokKind != tok::r_paren) + return SourceLocation(); + return RParenLoc; +} + +/// Determine if the statement needs braces around it, and add them if it does. +/// Returns true if braces where added. +bool BracesAroundStatementsCheck::checkStmt( + const MatchFinder::MatchResult &Result, const Stmt *S, + SourceLocation InitialLoc, SourceLocation EndLocHint) { + + while (const auto *AS = dyn_cast<AttributedStmt>(S)) + S = AS->getSubStmt(); + + // 1) If there's a corresponding "else" or "while", the check inserts "} " + // right before that token. + // 2) If there's a multi-line block comment starting on the same line after + // the location we're inserting the closing brace at, or there's a non-comment + // token, the check inserts "\n}" right before that token. + // 3) Otherwise the check finds the end of line (possibly after some block or + // line comments) and inserts "\n}" right before that EOL. + if (!S || isa<CompoundStmt>(S)) { + // Already inside braces. + return false; + } + + if (!InitialLoc.isValid()) + return false; + const SourceManager &SM = *Result.SourceManager; + const ASTContext *Context = Result.Context; + + // Convert InitialLoc to file location, if it's on the same macro expansion + // level as the start of the statement. We also need file locations for + // Lexer::getLocForEndOfToken working properly. + InitialLoc = Lexer::makeFileCharRange( + CharSourceRange::getCharRange(InitialLoc, S->getBeginLoc()), + SM, Context->getLangOpts()) + .getBegin(); + if (InitialLoc.isInvalid()) + return false; + SourceLocation StartLoc = + Lexer::getLocForEndOfToken(InitialLoc, 0, SM, Context->getLangOpts()); + + // StartLoc points at the location of the opening brace to be inserted. + SourceLocation EndLoc; + std::string ClosingInsertion; + if (EndLocHint.isValid()) { + EndLoc = EndLocHint; + ClosingInsertion = "} "; + } else { + EndLoc = findEndLocation(*S, SM, Context); + ClosingInsertion = "\n}"; + } + + assert(StartLoc.isValid()); + + // Don't require braces for statements spanning less than certain number of + // lines. + if (ShortStatementLines && !ForceBracesStmts.erase(S)) { + unsigned StartLine = SM.getSpellingLineNumber(StartLoc); + unsigned EndLine = SM.getSpellingLineNumber(EndLoc); + if (EndLine - StartLine < ShortStatementLines) + return false; + } + + auto Diag = diag(StartLoc, "statement should be inside braces"); + + // Change only if StartLoc and EndLoc are on the same macro expansion level. + // This will also catch invalid EndLoc. + // Example: LLVM_DEBUG( for(...) do_something() ); + // In this case fix-it cannot be provided as the semicolon which is not + // visible here is part of the macro. Adding braces here would require adding + // another semicolon. + if (Lexer::makeFileCharRange( + CharSourceRange::getTokenRange(SourceRange( + SM.getSpellingLoc(StartLoc), SM.getSpellingLoc(EndLoc))), + SM, Context->getLangOpts()) + .isInvalid()) + return false; + + Diag << FixItHint::CreateInsertion(StartLoc, " {") + << FixItHint::CreateInsertion(EndLoc, ClosingInsertion); + return true; +} + +void BracesAroundStatementsCheck::onEndOfTranslationUnit() { + ForceBracesStmts.clear(); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/BracesAroundStatementsCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/BracesAroundStatementsCheck.h new file mode 100644 index 0000000000..9653dd30f0 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/BracesAroundStatementsCheck.h @@ -0,0 +1,67 @@ +//===--- BracesAroundStatementsCheck.h - clang-tidy -------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_BRACESAROUNDSTATEMENTSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_BRACESAROUNDSTATEMENTSCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Checks that bodies of `if` statements and loops (`for`, `range-for`, +/// `do-while`, and `while`) are inside braces +/// +/// Before: +/// +/// \code +/// if (condition) +/// statement; +/// \endcode +/// +/// After: +/// +/// \code +/// if (condition) { +/// statement; +/// } +/// \endcode +/// +/// Additionally, one can define an option `ShortStatementLines` defining the +/// minimal number of lines that the statement should have in order to trigger +/// this check. +/// +/// The number of lines is counted from the end of condition or initial keyword +/// (`do`/`else`) until the last line of the inner statement. Default value 0 +/// means that braces will be added to all statements (not having them already). +class BracesAroundStatementsCheck : public ClangTidyCheck { +public: + BracesAroundStatementsCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + +private: + bool checkStmt(const ast_matchers::MatchFinder::MatchResult &Result, + const Stmt *S, SourceLocation StartLoc, + SourceLocation EndLocHint = SourceLocation()); + template <typename IfOrWhileStmt> + SourceLocation findRParenLoc(const IfOrWhileStmt *S, const SourceManager &SM, + const ASTContext *Context); + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + std::set<const Stmt *> ForceBracesStmts; + const unsigned ShortStatementLines; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_BRACESAROUNDSTATEMENTSCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ConstReturnTypeCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ConstReturnTypeCheck.cpp new file mode 100644 index 0000000000..e92350632b --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ConstReturnTypeCheck.cpp @@ -0,0 +1,159 @@ +//===--- ConstReturnTypeCheck.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 "ConstReturnTypeCheck.h" +#include "../utils/LexerUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Lexer.h" +#include <optional> + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +// Finds the location of the qualifying `const` token in the `FunctionDecl`'s +// return type. Returns `std::nullopt` when the return type is not +// `const`-qualified or `const` does not appear in `Def`'s source, like when the +// type is an alias or a macro. +static std::optional<Token> +findConstToRemove(const FunctionDecl *Def, + const MatchFinder::MatchResult &Result) { + if (!Def->getReturnType().isLocalConstQualified()) + return std::nullopt; + + // Get the begin location for the function name, including any qualifiers + // written in the source (for out-of-line declarations). A FunctionDecl's + // "location" is the start of its name, so, when the name is unqualified, we + // use `getLocation()`. + SourceLocation NameBeginLoc = Def->getQualifier() + ? Def->getQualifierLoc().getBeginLoc() + : Def->getLocation(); + // Since either of the locs can be in a macro, use `makeFileCharRange` to be + // sure that we have a consistent `CharSourceRange`, located entirely in the + // source file. + CharSourceRange FileRange = Lexer::makeFileCharRange( + CharSourceRange::getCharRange(Def->getBeginLoc(), NameBeginLoc), + *Result.SourceManager, Result.Context->getLangOpts()); + + if (FileRange.isInvalid()) + return std::nullopt; + + return utils::lexer::getQualifyingToken( + tok::kw_const, FileRange, *Result.Context, *Result.SourceManager); +} + +namespace { + +AST_MATCHER(QualType, isLocalConstQualified) { + return Node.isLocalConstQualified(); +} + +AST_MATCHER(QualType, isTypeOfType) { + return isa<TypeOfType>(Node.getTypePtr()); +} + +AST_MATCHER(QualType, isTypeOfExprType) { + return isa<TypeOfExprType>(Node.getTypePtr()); +} + +struct CheckResult { + // Source range of the relevant `const` token in the definition being checked. + CharSourceRange ConstRange; + + // FixItHints associated with the definition being checked. + llvm::SmallVector<clang::FixItHint, 4> Hints; + + // Locations of any declarations that could not be fixed. + llvm::SmallVector<clang::SourceLocation, 4> DeclLocs; +}; + +} // namespace + +// Does the actual work of the check. +static CheckResult checkDef(const clang::FunctionDecl *Def, + const MatchFinder::MatchResult &MatchResult) { + CheckResult Result; + std::optional<Token> Tok = findConstToRemove(Def, MatchResult); + if (!Tok) + return Result; + + Result.ConstRange = + CharSourceRange::getCharRange(Tok->getLocation(), Tok->getEndLoc()); + Result.Hints.push_back(FixItHint::CreateRemoval(Result.ConstRange)); + + // Fix the definition and any visible declarations, but don't warn + // separately for each declaration. Instead, associate all fixes with the + // single warning at the definition. + for (const FunctionDecl *Decl = Def->getPreviousDecl(); Decl != nullptr; + Decl = Decl->getPreviousDecl()) { + if (std::optional<Token> T = findConstToRemove(Decl, MatchResult)) + Result.Hints.push_back(FixItHint::CreateRemoval( + CharSourceRange::getCharRange(T->getLocation(), T->getEndLoc()))); + else + // `getInnerLocStart` gives the start of the return type. + Result.DeclLocs.push_back(Decl->getInnerLocStart()); + } + return Result; +} + +void ConstReturnTypeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreMacros", IgnoreMacros); +} + +void ConstReturnTypeCheck::registerMatchers(MatchFinder *Finder) { + // Find all function definitions for which the return types are `const` + // qualified, ignoring decltype types. + auto NonLocalConstType = + qualType(unless(isLocalConstQualified()), + anyOf(decltypeType(), autoType(), isTypeOfType(), + isTypeOfExprType(), substTemplateTypeParmType())); + Finder->addMatcher( + functionDecl( + returns(allOf(isConstQualified(), unless(NonLocalConstType))), + anyOf(isDefinition(), cxxMethodDecl(isPure())), + // Overridden functions are not actionable. + unless(cxxMethodDecl(isOverride()))) + .bind("func"), + this); +} + +void ConstReturnTypeCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Def = Result.Nodes.getNodeAs<FunctionDecl>("func"); + // Suppress the check if macros are involved. + if (IgnoreMacros && + (Def->getBeginLoc().isMacroID() || Def->getEndLoc().isMacroID())) + return; + + CheckResult CR = checkDef(Def, Result); + { + // Clang only supports one in-flight diagnostic at a time. So, delimit the + // scope of `Diagnostic` to allow further diagnostics after the scope. We + // use `getInnerLocStart` to get the start of the return type. + DiagnosticBuilder Diagnostic = + diag(Def->getInnerLocStart(), + "return type %0 is 'const'-qualified at the top level, which may " + "reduce code readability without improving const correctness") + << Def->getReturnType(); + if (CR.ConstRange.isValid()) + Diagnostic << CR.ConstRange; + + // Do not propose fixes for virtual function. + const auto *Method = dyn_cast<CXXMethodDecl>(Def); + if (Method && Method->isVirtual()) + return; + + for (auto &Hint : CR.Hints) + Diagnostic << Hint; + } + for (auto Loc : CR.DeclLocs) + diag(Loc, "could not transform this declaration", DiagnosticIDs::Note); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ConstReturnTypeCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ConstReturnTypeCheck.h new file mode 100644 index 0000000000..c3d779eafe --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ConstReturnTypeCheck.h @@ -0,0 +1,36 @@ +//===--- ConstReturnTypeCheck.h - clang-tidy --------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONSTRETURNTYPECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONSTRETURNTYPECHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// For any function whose return type is const-qualified, suggests removal of +/// the `const` qualifier from that return type. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/const-return-type.html +class ConstReturnTypeCheck : public ClangTidyCheck { + public: + ConstReturnTypeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + + private: + const bool IgnoreMacros; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONSTRETURNTYPECHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerContainsCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerContainsCheck.cpp new file mode 100644 index 0000000000..970ed8b83e --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerContainsCheck.cpp @@ -0,0 +1,138 @@ +//===--- ContainerContainsCheck.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 "ContainerContainsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) { + const auto SupportedContainers = hasType( + hasUnqualifiedDesugaredType(recordType(hasDeclaration(cxxRecordDecl( + hasAnyName("::std::set", "::std::unordered_set", "::std::map", + "::std::unordered_map", "::std::multiset", + "::std::unordered_multiset", "::std::multimap", + "::std::unordered_multimap")))))); + + const auto CountCall = + cxxMemberCallExpr(on(SupportedContainers), + callee(cxxMethodDecl(hasName("count"))), + argumentCountIs(1)) + .bind("call"); + + const auto FindCall = + cxxMemberCallExpr(on(SupportedContainers), + callee(cxxMethodDecl(hasName("find"))), + argumentCountIs(1)) + .bind("call"); + + const auto EndCall = cxxMemberCallExpr(on(SupportedContainers), + callee(cxxMethodDecl(hasName("end"))), + argumentCountIs(0)); + + const auto Literal0 = integerLiteral(equals(0)); + const auto Literal1 = integerLiteral(equals(1)); + + auto AddSimpleMatcher = [&](auto Matcher) { + Finder->addMatcher( + traverse(TK_IgnoreUnlessSpelledInSource, std::move(Matcher)), this); + }; + + // Find membership tests which use `count()`. + Finder->addMatcher(implicitCastExpr(hasImplicitDestinationType(booleanType()), + hasSourceExpression(CountCall)) + .bind("positiveComparison"), + this); + AddSimpleMatcher( + binaryOperator(hasLHS(CountCall), hasOperatorName("!="), hasRHS(Literal0)) + .bind("positiveComparison")); + AddSimpleMatcher( + binaryOperator(hasLHS(Literal0), hasOperatorName("!="), hasRHS(CountCall)) + .bind("positiveComparison")); + AddSimpleMatcher( + binaryOperator(hasLHS(CountCall), hasOperatorName(">"), hasRHS(Literal0)) + .bind("positiveComparison")); + AddSimpleMatcher( + binaryOperator(hasLHS(Literal0), hasOperatorName("<"), hasRHS(CountCall)) + .bind("positiveComparison")); + AddSimpleMatcher( + binaryOperator(hasLHS(CountCall), hasOperatorName(">="), hasRHS(Literal1)) + .bind("positiveComparison")); + AddSimpleMatcher( + binaryOperator(hasLHS(Literal1), hasOperatorName("<="), hasRHS(CountCall)) + .bind("positiveComparison")); + + // Find inverted membership tests which use `count()`. + AddSimpleMatcher( + binaryOperator(hasLHS(CountCall), hasOperatorName("=="), hasRHS(Literal0)) + .bind("negativeComparison")); + AddSimpleMatcher( + binaryOperator(hasLHS(Literal0), hasOperatorName("=="), hasRHS(CountCall)) + .bind("negativeComparison")); + AddSimpleMatcher( + binaryOperator(hasLHS(CountCall), hasOperatorName("<="), hasRHS(Literal0)) + .bind("negativeComparison")); + AddSimpleMatcher( + binaryOperator(hasLHS(Literal0), hasOperatorName(">="), hasRHS(CountCall)) + .bind("negativeComparison")); + AddSimpleMatcher( + binaryOperator(hasLHS(CountCall), hasOperatorName("<"), hasRHS(Literal1)) + .bind("negativeComparison")); + AddSimpleMatcher( + binaryOperator(hasLHS(Literal1), hasOperatorName(">"), hasRHS(CountCall)) + .bind("negativeComparison")); + + // Find membership tests based on `find() == end()`. + AddSimpleMatcher( + binaryOperator(hasLHS(FindCall), hasOperatorName("!="), hasRHS(EndCall)) + .bind("positiveComparison")); + AddSimpleMatcher( + binaryOperator(hasLHS(FindCall), hasOperatorName("=="), hasRHS(EndCall)) + .bind("negativeComparison")); +} + +void ContainerContainsCheck::check(const MatchFinder::MatchResult &Result) { + // Extract the information about the match + const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call"); + const auto *PositiveComparison = + Result.Nodes.getNodeAs<Expr>("positiveComparison"); + const auto *NegativeComparison = + Result.Nodes.getNodeAs<Expr>("negativeComparison"); + assert((!PositiveComparison || !NegativeComparison) && + "only one of PositiveComparison or NegativeComparison should be set"); + bool Negated = NegativeComparison != nullptr; + const auto *Comparison = Negated ? NegativeComparison : PositiveComparison; + + // Diagnose the issue. + auto Diag = + diag(Call->getExprLoc(), "use 'contains' to check for membership"); + + // Don't fix it if it's in a macro invocation. Leave fixing it to the user. + SourceLocation FuncCallLoc = Comparison->getEndLoc(); + if (!FuncCallLoc.isValid() || FuncCallLoc.isMacroID()) + return; + + // Create the fix it. + const auto *Member = cast<MemberExpr>(Call->getCallee()); + Diag << FixItHint::CreateReplacement( + Member->getMemberNameInfo().getSourceRange(), "contains"); + SourceLocation ComparisonBegin = Comparison->getSourceRange().getBegin(); + SourceLocation ComparisonEnd = Comparison->getSourceRange().getEnd(); + SourceLocation CallBegin = Call->getSourceRange().getBegin(); + SourceLocation CallEnd = Call->getSourceRange().getEnd(); + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(ComparisonBegin, CallBegin), + Negated ? "!" : ""); + Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange( + CallEnd.getLocWithOffset(1), ComparisonEnd.getLocWithOffset(1))); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerContainsCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerContainsCheck.h new file mode 100644 index 0000000000..2e8276d684 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerContainsCheck.h @@ -0,0 +1,36 @@ +//===--- ContainerContainsCheck.h - clang-tidy ------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERCONTAINSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERCONTAINSCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Finds usages of `container.count()` and `find() == end()` which should be +/// replaced by a call to the `container.contains()` method introduced in C++20. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/container-contains.html +class ContainerContainsCheck : public ClangTidyCheck { +public: + ContainerContainsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) final; + void check(const ast_matchers::MatchFinder::MatchResult &Result) final; + +protected: + bool isLanguageVersionSupported(const LangOptions &LO) const final { + return LO.CPlusPlus20; + } +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERCONTAINSCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerDataPointerCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerDataPointerCheck.cpp new file mode 100644 index 0000000000..5a51b2567d --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerDataPointerCheck.cpp @@ -0,0 +1,115 @@ +//===--- ContainerDataPointerCheck.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 "ContainerDataPointerCheck.h" + +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringRef.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +constexpr llvm::StringLiteral ContainerExprName = "container-expr"; +constexpr llvm::StringLiteral DerefContainerExprName = "deref-container-expr"; +constexpr llvm::StringLiteral AddrOfContainerExprName = + "addr-of-container-expr"; +constexpr llvm::StringLiteral AddressOfName = "address-of"; + +ContainerDataPointerCheck::ContainerDataPointerCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + +void ContainerDataPointerCheck::registerMatchers(MatchFinder *Finder) { + const auto Record = + cxxRecordDecl( + isSameOrDerivedFrom( + namedDecl( + has(cxxMethodDecl(isPublic(), hasName("data")).bind("data"))) + .bind("container"))) + .bind("record"); + + const auto NonTemplateContainerType = + qualType(hasUnqualifiedDesugaredType(recordType(hasDeclaration(Record)))); + const auto TemplateContainerType = + qualType(hasUnqualifiedDesugaredType(templateSpecializationType( + hasDeclaration(classTemplateDecl(has(Record)))))); + + const auto Container = + qualType(anyOf(NonTemplateContainerType, TemplateContainerType)); + + const auto ContainerExpr = anyOf( + unaryOperator( + hasOperatorName("*"), + hasUnaryOperand( + expr(hasType(pointsTo(Container))).bind(DerefContainerExprName))) + .bind(ContainerExprName), + unaryOperator(hasOperatorName("&"), + hasUnaryOperand(expr(anyOf(hasType(Container), + hasType(references(Container)))) + .bind(AddrOfContainerExprName))) + .bind(ContainerExprName), + expr(anyOf(hasType(Container), hasType(pointsTo(Container)), + hasType(references(Container)))) + .bind(ContainerExprName)); + + const auto Zero = integerLiteral(equals(0)); + + const auto SubscriptOperator = callee(cxxMethodDecl(hasName("operator[]"))); + + Finder->addMatcher( + unaryOperator( + unless(isExpansionInSystemHeader()), hasOperatorName("&"), + hasUnaryOperand(expr( + anyOf(cxxOperatorCallExpr(SubscriptOperator, argumentCountIs(2), + hasArgument(0, ContainerExpr), + hasArgument(1, Zero)), + cxxMemberCallExpr(SubscriptOperator, on(ContainerExpr), + argumentCountIs(1), hasArgument(0, Zero)), + arraySubscriptExpr(hasLHS(ContainerExpr), hasRHS(Zero)))))) + .bind(AddressOfName), + this); +} + +void ContainerDataPointerCheck::check(const MatchFinder::MatchResult &Result) { + const auto *UO = Result.Nodes.getNodeAs<UnaryOperator>(AddressOfName); + const auto *CE = Result.Nodes.getNodeAs<Expr>(ContainerExprName); + const auto *DCE = Result.Nodes.getNodeAs<Expr>(DerefContainerExprName); + const auto *ACE = Result.Nodes.getNodeAs<Expr>(AddrOfContainerExprName); + + if (!UO || !CE) + return; + + if (DCE && !CE->getType()->isPointerType()) + CE = DCE; + else if (ACE) + CE = ACE; + + SourceRange SrcRange = CE->getSourceRange(); + + std::string ReplacementText{ + Lexer::getSourceText(CharSourceRange::getTokenRange(SrcRange), + *Result.SourceManager, getLangOpts())}; + + if (!isa<DeclRefExpr, ArraySubscriptExpr, CXXOperatorCallExpr, CallExpr, + MemberExpr>(CE)) + ReplacementText = "(" + ReplacementText + ")"; + + if (CE->getType()->isPointerType()) + ReplacementText += "->data()"; + else + ReplacementText += ".data()"; + + FixItHint Hint = + FixItHint::CreateReplacement(UO->getSourceRange(), ReplacementText); + diag(UO->getBeginLoc(), + "'data' should be used for accessing the data pointer instead of taking " + "the address of the 0-th element") + << Hint; +} +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerDataPointerCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerDataPointerCheck.h new file mode 100644 index 0000000000..65f78b8f6f --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerDataPointerCheck.h @@ -0,0 +1,41 @@ +//===--- ContainerDataPointerCheck.h - clang-tidy ---------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERDATAPOINTERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERDATAPOINTERCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { +/// Checks whether a call to `operator[]` and `&` can be replaced with a call to +/// `data()`. +/// +/// This only replaces the case where the offset being accessed through the +/// subscript operation is a known constant 0. This avoids a potential invalid +/// memory access when the container is empty. Cases where the constant is not +/// explicitly zero can be addressed through the clang static analyzer, and +/// those which cannot be statically identified can be caught using UBSan. +class ContainerDataPointerCheck : public ClangTidyCheck { +public: + ContainerDataPointerCheck(StringRef Name, ClangTidyContext *Context); + + bool isLanguageVersionSupported(const LangOptions &LO) const override { + return LO.CPlusPlus11; + } + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } +}; +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERDATAPOINTERCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp new file mode 100644 index 0000000000..a39b72e39c --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp @@ -0,0 +1,331 @@ +//===--- ContainerSizeEmptyCheck.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 "ContainerSizeEmptyCheck.h" +#include "../utils/ASTUtils.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringRef.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace ast_matchers { +AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam, + AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr, + CXXConstructExpr), + internal::Matcher<Expr>, ArgMatcher, + internal::Matcher<ParmVarDecl>, ParamMatcher) { + BoundNodesTreeBuilder Result; + // The first argument of an overloaded member operator is the implicit object + // argument of the method which should not be matched against a parameter, so + // we skip over it here. + BoundNodesTreeBuilder Matches; + unsigned ArgIndex = cxxOperatorCallExpr(callee(cxxMethodDecl())) + .matches(Node, Finder, &Matches) + ? 1 + : 0; + int ParamIndex = 0; + for (; ArgIndex < Node.getNumArgs(); ++ArgIndex) { + BoundNodesTreeBuilder ArgMatches(*Builder); + if (ArgMatcher.matches(*(Node.getArg(ArgIndex)->IgnoreParenCasts()), Finder, + &ArgMatches)) { + BoundNodesTreeBuilder ParamMatches(ArgMatches); + if (expr(anyOf(cxxConstructExpr(hasDeclaration(cxxConstructorDecl( + hasParameter(ParamIndex, ParamMatcher)))), + callExpr(callee(functionDecl( + hasParameter(ParamIndex, ParamMatcher)))))) + .matches(Node, Finder, &ParamMatches)) { + Result.addMatch(ParamMatches); + *Builder = std::move(Result); + return true; + } + } + ++ParamIndex; + } + return false; +} + +AST_MATCHER(Expr, usedInBooleanContext) { + const char *ExprName = "__booleanContextExpr"; + auto Result = + expr(expr().bind(ExprName), + anyOf(hasParent( + mapAnyOf(varDecl, fieldDecl).with(hasType(booleanType()))), + hasParent(cxxConstructorDecl( + hasAnyConstructorInitializer(cxxCtorInitializer( + withInitializer(expr(equalsBoundNode(ExprName))), + forField(hasType(booleanType())))))), + hasParent(stmt(anyOf( + explicitCastExpr(hasDestinationType(booleanType())), + mapAnyOf(ifStmt, doStmt, whileStmt, forStmt, + conditionalOperator) + .with(hasCondition(expr(equalsBoundNode(ExprName)))), + parenListExpr(hasParent(varDecl(hasType(booleanType())))), + parenExpr(hasParent( + explicitCastExpr(hasDestinationType(booleanType())))), + returnStmt(forFunction(returns(booleanType()))), + cxxUnresolvedConstructExpr(hasType(booleanType())), + invocation(hasAnyArgumentWithParam( + expr(equalsBoundNode(ExprName)), + parmVarDecl(hasType(booleanType())))), + binaryOperator(hasAnyOperatorName("&&", "||")), + unaryOperator(hasOperatorName("!")).bind("NegOnSize")))))) + .matches(Node, Finder, Builder); + Builder->removeBindings([ExprName](const BoundNodesMap &Nodes) { + return Nodes.getNode(ExprName).getNodeKind().isNone(); + }); + return Result; +} +AST_MATCHER(CXXConstructExpr, isDefaultConstruction) { + return Node.getConstructor()->isDefaultConstructor(); +} +AST_MATCHER(QualType, isIntegralType) { + return Node->isIntegralType(Finder->getASTContext()); +} +} // namespace ast_matchers +namespace tidy::readability { + +using utils::isBinaryOrTernary; + +ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + +void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) { + const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom( + namedDecl( + has(cxxMethodDecl( + isConst(), parameterCountIs(0), isPublic(), hasName("size"), + returns(qualType(isIntegralType(), unless(booleanType())))) + .bind("size")), + has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(), + hasName("empty"), returns(booleanType())) + .bind("empty"))) + .bind("container"))); + + const auto ValidContainerNonTemplateType = + qualType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(ValidContainerRecord)))); + const auto ValidContainerTemplateType = + qualType(hasUnqualifiedDesugaredType(templateSpecializationType( + hasDeclaration(classTemplateDecl(has(ValidContainerRecord)))))); + + const auto ValidContainer = qualType( + anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType)); + + const auto WrongUse = + anyOf(hasParent(binaryOperator( + isComparisonOperator(), + hasEitherOperand(anyOf(integerLiteral(equals(1)), + integerLiteral(equals(0))))) + .bind("SizeBinaryOp")), + usedInBooleanContext()); + + Finder->addMatcher( + cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer), + hasType(pointsTo(ValidContainer)), + hasType(references(ValidContainer)))) + .bind("MemberCallObject")), + callee(cxxMethodDecl(hasName("size"))), WrongUse, + unless(hasAncestor(cxxMethodDecl( + ofClass(equalsBoundNode("container")))))) + .bind("SizeCallExpr"), + this); + + Finder->addMatcher( + callExpr(has(cxxDependentScopeMemberExpr( + hasObjectExpression( + expr(anyOf(hasType(ValidContainer), + hasType(pointsTo(ValidContainer)), + hasType(references(ValidContainer)))) + .bind("MemberCallObject")), + hasMemberName("size"))), + WrongUse, + unless(hasAncestor( + cxxMethodDecl(ofClass(equalsBoundNode("container")))))) + .bind("SizeCallExpr"), + this); + + // Comparison to empty string or empty constructor. + const auto WrongComparend = anyOf( + stringLiteral(hasSize(0)), cxxConstructExpr(isDefaultConstruction()), + cxxUnresolvedConstructExpr(argumentCountIs(0))); + // Match the object being compared. + const auto STLArg = + anyOf(unaryOperator( + hasOperatorName("*"), + hasUnaryOperand( + expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))), + expr(hasType(ValidContainer)).bind("STLObject")); + Finder->addMatcher( + binaryOperation(hasAnyOperatorName("==", "!="), + hasOperands(WrongComparend, + STLArg), + unless(hasAncestor(cxxMethodDecl( + ofClass(equalsBoundNode("container")))))) + .bind("BinCmp"), + this); +} + +void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) { + const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr"); + const auto *MemberCallObject = + Result.Nodes.getNodeAs<Expr>("MemberCallObject"); + const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp"); + const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp"); + const auto *BinCmpRewritten = + Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>("BinCmp"); + const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp"); + const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee"); + const auto *E = + MemberCallObject + ? MemberCallObject + : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject")); + FixItHint Hint; + std::string ReplacementText = std::string( + Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()), + *Result.SourceManager, getLangOpts())); + const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E); + if (isBinaryOrTernary(E) || isa<UnaryOperator>(E) || + (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) { + ReplacementText = "(" + ReplacementText + ")"; + } + if (OpCallExpr && + OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) { + // This can happen if the object is a smart pointer. Don't add anything + // because a '->' is already there (PR#51776), just call the method. + ReplacementText += "empty()"; + } else if (E->getType()->isPointerType()) + ReplacementText += "->empty()"; + else + ReplacementText += ".empty()"; + + if (BinCmp) { + if (BinCmp->getOperator() == OO_ExclaimEqual) { + ReplacementText = "!" + ReplacementText; + } + Hint = + FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText); + } else if (BinCmpTempl) { + if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE) { + ReplacementText = "!" + ReplacementText; + } + Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(), + ReplacementText); + } else if (BinCmpRewritten) { + if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE) { + ReplacementText = "!" + ReplacementText; + } + Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(), + ReplacementText); + } else if (BinaryOp) { // Determine the correct transformation. + const auto *LiteralLHS = + llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts()); + const auto *LiteralRHS = + llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts()); + const bool ContainerIsLHS = !LiteralLHS; + + uint64_t Value = 0; + if (LiteralLHS) + Value = LiteralLHS->getValue().getLimitedValue(); + else if (LiteralRHS) + Value = LiteralRHS->getValue().getLimitedValue(); + else + return; + + bool Negation = false; + const auto OpCode = BinaryOp->getOpcode(); + + // Constant that is not handled. + if (Value > 1) + return; + + if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ || + OpCode == BinaryOperatorKind::BO_NE)) + return; + + // Always true, no warnings for that. + if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) || + (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS)) + return; + + // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size. + if (Value == 1) { + if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) || + (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS)) + return; + if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) || + (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS)) + return; + } + + if (OpCode == BinaryOperatorKind::BO_NE && Value == 0) + Negation = true; + if ((OpCode == BinaryOperatorKind::BO_GT || + OpCode == BinaryOperatorKind::BO_GE) && + ContainerIsLHS) + Negation = true; + if ((OpCode == BinaryOperatorKind::BO_LT || + OpCode == BinaryOperatorKind::BO_LE) && + !ContainerIsLHS) + Negation = true; + + if (Negation) + ReplacementText = "!" + ReplacementText; + Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(), + ReplacementText); + + } else { + // If there is a conversion above the size call to bool, it is safe to just + // replace size with empty. + if (const auto *UnaryOp = + Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize")) + Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(), + ReplacementText); + else + Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(), + "!" + ReplacementText); + } + + auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{}; + + if (WarnLoc.isValid()) { + diag(WarnLoc, "the 'empty' method should be used to check " + "for emptiness instead of 'size'") + << Hint; + } else { + WarnLoc = BinCmpTempl + ? BinCmpTempl->getBeginLoc() + : (BinCmp ? BinCmp->getBeginLoc() + : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc() + : SourceLocation{})); + diag(WarnLoc, "the 'empty' method should be used to check " + "for emptiness instead of comparing to an empty object") + << Hint; + } + + const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container"); + if (const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) { + // The definition of the empty() method is the same for all implicit + // instantiations. In order to avoid duplicate or inconsistent warnings + // (depending on how deduplication is done), we use the same class name + // for all implicit instantiations of a template. + if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation) + Container = CTS->getSpecializedTemplate(); + } + const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty"); + + diag(Empty->getLocation(), "method %0::empty() defined here", + DiagnosticIDs::Note) + << Container; +} + +} // namespace tidy::readability +} // namespace clang diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerSizeEmptyCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerSizeEmptyCheck.h new file mode 100644 index 0000000000..90d1839395 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ContainerSizeEmptyCheck.h @@ -0,0 +1,41 @@ +//===--- ContainerSizeEmptyCheck.h - clang-tidy -----------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERSIZEEMPTYCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERSIZEEMPTYCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Checks whether a call to the `size()` method can be replaced with a call to +/// `empty()`. +/// +/// The emptiness of a container should be checked using the `empty()` method +/// instead of the `size()` method. It is not guaranteed that `size()` is a +/// constant-time function, and it is generally more efficient and also shows +/// clearer intent to use `empty()`. Furthermore some containers may implement +/// the `empty()` method but not implement the `size()` method. Using `empty()` +/// whenever possible makes it easier to switch to another container in the +/// future. +class ContainerSizeEmptyCheck : public ClangTidyCheck { +public: + ContainerSizeEmptyCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERSIZEEMPTYCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.cpp new file mode 100644 index 0000000000..0ef7d2955f --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.cpp @@ -0,0 +1,168 @@ +//===--- ConvertMemberFunctionsToStatic.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 "ConvertMemberFunctionsToStatic.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } + +AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); } + +AST_MATCHER(CXXMethodDecl, isOverloadedOperator) { + return Node.isOverloadedOperator(); +} + +AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) { + return Node.hasAnyDependentBases(); +} + +AST_MATCHER(CXXMethodDecl, isTemplate) { + return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate; +} + +AST_MATCHER(CXXMethodDecl, isDependentContext) { + return Node.isDependentContext(); +} + +AST_MATCHER(CXXMethodDecl, isInsideMacroDefinition) { + const ASTContext &Ctxt = Finder->getASTContext(); + return clang::Lexer::makeFileCharRange( + clang::CharSourceRange::getCharRange( + Node.getTypeSourceInfo()->getTypeLoc().getSourceRange()), + Ctxt.getSourceManager(), Ctxt.getLangOpts()) + .isInvalid(); +} + +AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl, + ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) { + return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder); +} + +AST_MATCHER(CXXMethodDecl, usesThis) { + class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> { + public: + bool Used = false; + + bool VisitCXXThisExpr(const CXXThisExpr *E) { + Used = true; + return false; // Stop traversal. + } + } UsageOfThis; + + // TraverseStmt does not modify its argument. + UsageOfThis.TraverseStmt(const_cast<Stmt *>(Node.getBody())); + + return UsageOfThis.Used; +} + +void ConvertMemberFunctionsToStatic::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + cxxMethodDecl( + isDefinition(), isUserProvided(), + unless(anyOf( + isExpansionInSystemHeader(), isVirtual(), isStatic(), + hasTrivialBody(), isOverloadedOperator(), cxxConstructorDecl(), + cxxDestructorDecl(), cxxConversionDecl(), isTemplate(), + isDependentContext(), + ofClass(anyOf( + isLambda(), + hasAnyDependentBases()) // Method might become virtual + // depending on template base class. + ), + isInsideMacroDefinition(), + hasCanonicalDecl(isInsideMacroDefinition()), usesThis()))) + .bind("x"), + this); +} + +/// Obtain the original source code text from a SourceRange. +static StringRef getStringFromRange(SourceManager &SourceMgr, + const LangOptions &LangOpts, + SourceRange Range) { + if (SourceMgr.getFileID(Range.getBegin()) != + SourceMgr.getFileID(Range.getEnd())) + return {}; + + return Lexer::getSourceText(CharSourceRange(Range, true), SourceMgr, + LangOpts); +} + +static SourceRange getLocationOfConst(const TypeSourceInfo *TSI, + SourceManager &SourceMgr, + const LangOptions &LangOpts) { + assert(TSI); + const auto FTL = TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>(); + assert(FTL); + + SourceRange Range{FTL.getRParenLoc().getLocWithOffset(1), + FTL.getLocalRangeEnd()}; + // Inside Range, there might be other keywords and trailing return types. + // Find the exact position of "const". + StringRef Text = getStringFromRange(SourceMgr, LangOpts, Range); + size_t Offset = Text.find("const"); + if (Offset == StringRef::npos) + return {}; + + SourceLocation Start = Range.getBegin().getLocWithOffset(Offset); + return {Start, Start.getLocWithOffset(strlen("const") - 1)}; +} + +void ConvertMemberFunctionsToStatic::check( + const MatchFinder::MatchResult &Result) { + const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>("x"); + + // TODO: For out-of-line declarations, don't modify the source if the header + // is excluded by the -header-filter option. + DiagnosticBuilder Diag = + diag(Definition->getLocation(), "method %0 can be made static") + << Definition; + + // TODO: Would need to remove those in a fix-it. + if (Definition->getMethodQualifiers().hasVolatile() || + Definition->getMethodQualifiers().hasRestrict() || + Definition->getRefQualifier() != RQ_None) + return; + + const CXXMethodDecl *Declaration = Definition->getCanonicalDecl(); + + if (Definition->isConst()) { + // Make sure that we either remove 'const' on both declaration and + // definition or emit no fix-it at all. + SourceRange DefConst = getLocationOfConst(Definition->getTypeSourceInfo(), + *Result.SourceManager, + Result.Context->getLangOpts()); + + if (DefConst.isInvalid()) + return; + + if (Declaration != Definition) { + SourceRange DeclConst = getLocationOfConst( + Declaration->getTypeSourceInfo(), *Result.SourceManager, + Result.Context->getLangOpts()); + + if (DeclConst.isInvalid()) + return; + Diag << FixItHint::CreateRemoval(DeclConst); + } + + // Remove existing 'const' from both declaration and definition. + Diag << FixItHint::CreateRemoval(DefConst); + } + Diag << FixItHint::CreateInsertion(Declaration->getBeginLoc(), "static "); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.h new file mode 100644 index 0000000000..79b0d39bed --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ConvertMemberFunctionsToStatic.h @@ -0,0 +1,32 @@ +//===--- ConvertMemberFunctionsToStatic.h - clang-tidy ----------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONVERTMEMFUNCTOSTATIC_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONVERTMEMFUNCTOSTATIC_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// This check finds C++ class methods than can be made static +/// because they don't use the 'this' pointer. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/ +/// readability-convert-member-functions-to-static.html +class ConvertMemberFunctionsToStatic : public ClangTidyCheck { +public: + ConvertMemberFunctionsToStatic(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONVERTMEMFUNCTOSTATIC_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/DeleteNullPointerCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/DeleteNullPointerCheck.cpp new file mode 100644 index 0000000000..12131cc078 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/DeleteNullPointerCheck.cpp @@ -0,0 +1,74 @@ +//===--- DeleteNullPointerCheck.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 "DeleteNullPointerCheck.h" +#include "../utils/LexerUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +void DeleteNullPointerCheck::registerMatchers(MatchFinder *Finder) { + const auto DeleteExpr = + cxxDeleteExpr( + has(declRefExpr(to(decl(equalsBoundNode("deletedPointer")))))) + .bind("deleteExpr"); + + const auto DeleteMemberExpr = + cxxDeleteExpr(has(memberExpr(hasDeclaration( + fieldDecl(equalsBoundNode("deletedMemberPointer")))))) + .bind("deleteMemberExpr"); + + const auto PointerExpr = anyOf( + declRefExpr(to(decl().bind("deletedPointer"))), + memberExpr(hasDeclaration(fieldDecl().bind("deletedMemberPointer")))); + + const auto BinaryPointerCheckCondition = binaryOperator(hasOperands( + anyOf(cxxNullPtrLiteralExpr(), integerLiteral(equals(0))), PointerExpr)); + + Finder->addMatcher( + ifStmt(hasCondition(anyOf(PointerExpr, BinaryPointerCheckCondition)), + hasThen(anyOf( + DeleteExpr, DeleteMemberExpr, + compoundStmt(anyOf(has(DeleteExpr), has(DeleteMemberExpr)), + statementCountIs(1)) + .bind("compound")))) + .bind("ifWithDelete"), + this); +} + +void DeleteNullPointerCheck::check(const MatchFinder::MatchResult &Result) { + const auto *IfWithDelete = Result.Nodes.getNodeAs<IfStmt>("ifWithDelete"); + const auto *Compound = Result.Nodes.getNodeAs<CompoundStmt>("compound"); + + auto Diag = diag( + IfWithDelete->getBeginLoc(), + "'if' statement is unnecessary; deleting null pointer has no effect"); + if (IfWithDelete->getElse()) + return; + // FIXME: generate fixit for this case. + + Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( + IfWithDelete->getBeginLoc(), + utils::lexer::getPreviousToken(IfWithDelete->getThen()->getBeginLoc(), + *Result.SourceManager, + Result.Context->getLangOpts()) + .getLocation())); + + if (Compound) { + Diag << FixItHint::CreateRemoval( + CharSourceRange::getTokenRange(Compound->getLBracLoc())); + Diag << FixItHint::CreateRemoval( + CharSourceRange::getTokenRange(Compound->getRBracLoc())); + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/DeleteNullPointerCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/DeleteNullPointerCheck.h new file mode 100644 index 0000000000..e9e7c942d1 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/DeleteNullPointerCheck.h @@ -0,0 +1,34 @@ +//===--- DeleteNullPointerCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETE_NULL_POINTER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETE_NULL_POINTER_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Check whether the 'if' statement is unnecessary before calling 'delete' on a +/// pointer. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/delete-null-pointer.html +class DeleteNullPointerCheck : public ClangTidyCheck { +public: + DeleteNullPointerCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DELETE_NULL_POINTER_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/DuplicateIncludeCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/DuplicateIncludeCheck.cpp new file mode 100644 index 0000000000..d1f41e0ec7 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/DuplicateIncludeCheck.cpp @@ -0,0 +1,112 @@ +//===--- DuplicateIncludeCheck.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 "DuplicateIncludeCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include <memory> + +namespace clang::tidy::readability { + +static SourceLocation advanceBeyondCurrentLine(const SourceManager &SM, + SourceLocation Start, + int Offset) { + const FileID Id = SM.getFileID(Start); + const unsigned LineNumber = SM.getSpellingLineNumber(Start); + while (SM.getFileID(Start) == Id && + SM.getSpellingLineNumber(Start.getLocWithOffset(Offset)) == LineNumber) + Start = Start.getLocWithOffset(Offset); + return Start; +} + +namespace { + +using FileList = SmallVector<StringRef>; + +class DuplicateIncludeCallbacks : public PPCallbacks { +public: + DuplicateIncludeCallbacks(DuplicateIncludeCheck &Check, + const SourceManager &SM) + : Check(Check), SM(SM) { + // The main file doesn't participate in the FileChanged notification. + Files.emplace_back(); + } + + void FileChanged(SourceLocation Loc, FileChangeReason Reason, + SrcMgr::CharacteristicKind FileType, + FileID PrevFID) override; + + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, + OptionalFileEntryRef File, StringRef SearchPath, + StringRef RelativePath, const Module *Imported, + SrcMgr::CharacteristicKind FileType) override; + + void MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) override; + + void MacroUndefined(const Token &MacroNameTok, const MacroDefinition &MD, + const MacroDirective *Undef) override; + +private: + // A list of included files is kept for each file we enter. + SmallVector<FileList> Files; + DuplicateIncludeCheck &Check; + const SourceManager &SM; +}; + +void DuplicateIncludeCallbacks::FileChanged(SourceLocation Loc, + FileChangeReason Reason, + SrcMgr::CharacteristicKind FileType, + FileID PrevFID) { + if (Reason == EnterFile) + Files.emplace_back(); + else if (Reason == ExitFile) + Files.pop_back(); +} + +void DuplicateIncludeCallbacks::InclusionDirective( + SourceLocation HashLoc, const Token &IncludeTok, StringRef FileName, + bool IsAngled, CharSourceRange FilenameRange, OptionalFileEntryRef File, + StringRef SearchPath, StringRef RelativePath, const Module *Imported, + SrcMgr::CharacteristicKind FileType) { + if (llvm::is_contained(Files.back(), FileName)) { + // We want to delete the entire line, so make sure that [Start,End] covers + // everything. + SourceLocation Start = + advanceBeyondCurrentLine(SM, HashLoc, -1).getLocWithOffset(-1); + SourceLocation End = + advanceBeyondCurrentLine(SM, FilenameRange.getEnd(), 1); + Check.diag(HashLoc, "duplicate include") + << FixItHint::CreateRemoval(SourceRange{Start, End}); + } else + Files.back().push_back(FileName); +} + +void DuplicateIncludeCallbacks::MacroDefined(const Token &MacroNameTok, + const MacroDirective *MD) { + Files.back().clear(); +} + +void DuplicateIncludeCallbacks::MacroUndefined(const Token &MacroNameTok, + const MacroDefinition &MD, + const MacroDirective *Undef) { + Files.back().clear(); +} + +} // namespace + +void DuplicateIncludeCheck::registerPPCallbacks( + const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { + PP->addPPCallbacks(std::make_unique<DuplicateIncludeCallbacks>(*this, SM)); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/DuplicateIncludeCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/DuplicateIncludeCheck.h new file mode 100644 index 0000000000..05395496d8 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/DuplicateIncludeCheck.h @@ -0,0 +1,31 @@ +//===--- DuplicateIncludeCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DUPLICATE_INCLUDE_CHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DUPLICATE_INCLUDE_CHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// \brief Find and remove duplicate #include directives. +/// +/// Only consecutive include directives without any other preprocessor +/// directives between them are analyzed. +class DuplicateIncludeCheck : public ClangTidyCheck { +public: + DuplicateIncludeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_DUPLICATE_INCLUDE_CHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ElseAfterReturnCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ElseAfterReturnCheck.cpp new file mode 100644 index 0000000000..1e85caf688 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ElseAfterReturnCheck.cpp @@ -0,0 +1,325 @@ +//===--- ElseAfterReturnCheck.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 "ElseAfterReturnCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/FixIt.h" +#include "llvm/ADT/SmallVector.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +namespace { + +class PPConditionalCollector : public PPCallbacks { +public: + PPConditionalCollector( + ElseAfterReturnCheck::ConditionalBranchMap &Collections, + const SourceManager &SM) + : Collections(Collections), SM(SM) {} + void Endif(SourceLocation Loc, SourceLocation IfLoc) override { + if (!SM.isWrittenInSameFile(Loc, IfLoc)) + return; + SmallVectorImpl<SourceRange> &Collection = Collections[SM.getFileID(Loc)]; + assert(Collection.empty() || Collection.back().getEnd() < Loc); + Collection.emplace_back(IfLoc, Loc); + } + +private: + ElseAfterReturnCheck::ConditionalBranchMap &Collections; + const SourceManager &SM; +}; + +} // namespace + +static const char InterruptingStr[] = "interrupting"; +static const char WarningMessage[] = "do not use 'else' after '%0'"; +static const char WarnOnUnfixableStr[] = "WarnOnUnfixable"; +static const char WarnOnConditionVariablesStr[] = "WarnOnConditionVariables"; + +static const DeclRefExpr *findUsage(const Stmt *Node, int64_t DeclIdentifier) { + if (!Node) + return nullptr; + if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Node)) { + if (DeclRef->getDecl()->getID() == DeclIdentifier) + return DeclRef; + } else { + for (const Stmt *ChildNode : Node->children()) { + if (const DeclRefExpr *Result = findUsage(ChildNode, DeclIdentifier)) + return Result; + } + } + return nullptr; +} + +static const DeclRefExpr * +findUsageRange(const Stmt *Node, + const llvm::ArrayRef<int64_t> &DeclIdentifiers) { + if (!Node) + return nullptr; + if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Node)) { + if (llvm::is_contained(DeclIdentifiers, DeclRef->getDecl()->getID())) + return DeclRef; + } else { + for (const Stmt *ChildNode : Node->children()) { + if (const DeclRefExpr *Result = + findUsageRange(ChildNode, DeclIdentifiers)) + return Result; + } + } + return nullptr; +} + +static const DeclRefExpr *checkInitDeclUsageInElse(const IfStmt *If) { + const auto *InitDeclStmt = dyn_cast_or_null<DeclStmt>(If->getInit()); + if (!InitDeclStmt) + return nullptr; + if (InitDeclStmt->isSingleDecl()) { + const Decl *InitDecl = InitDeclStmt->getSingleDecl(); + assert(isa<VarDecl>(InitDecl) && "SingleDecl must be a VarDecl"); + return findUsage(If->getElse(), InitDecl->getID()); + } + llvm::SmallVector<int64_t, 4> DeclIdentifiers; + for (const Decl *ChildDecl : InitDeclStmt->decls()) { + assert(isa<VarDecl>(ChildDecl) && "Init Decls must be a VarDecl"); + DeclIdentifiers.push_back(ChildDecl->getID()); + } + return findUsageRange(If->getElse(), DeclIdentifiers); +} + +static const DeclRefExpr *checkConditionVarUsageInElse(const IfStmt *If) { + if (const VarDecl *CondVar = If->getConditionVariable()) + return findUsage(If->getElse(), CondVar->getID()); + return nullptr; +} + +static bool containsDeclInScope(const Stmt *Node) { + if (isa<DeclStmt>(Node)) + return true; + if (const auto *Compound = dyn_cast<CompoundStmt>(Node)) + return llvm::any_of(Compound->body(), [](const Stmt *SubNode) { + return isa<DeclStmt>(SubNode); + }); + return false; +} + +static void removeElseAndBrackets(DiagnosticBuilder &Diag, ASTContext &Context, + const Stmt *Else, SourceLocation ElseLoc) { + auto Remap = [&](SourceLocation Loc) { + return Context.getSourceManager().getExpansionLoc(Loc); + }; + auto TokLen = [&](SourceLocation Loc) { + return Lexer::MeasureTokenLength(Loc, Context.getSourceManager(), + Context.getLangOpts()); + }; + + if (const auto *CS = dyn_cast<CompoundStmt>(Else)) { + Diag << tooling::fixit::createRemoval(ElseLoc); + SourceLocation LBrace = CS->getLBracLoc(); + SourceLocation RBrace = CS->getRBracLoc(); + SourceLocation RangeStart = + Remap(LBrace).getLocWithOffset(TokLen(LBrace) + 1); + SourceLocation RangeEnd = Remap(RBrace).getLocWithOffset(-1); + + llvm::StringRef Repl = Lexer::getSourceText( + CharSourceRange::getTokenRange(RangeStart, RangeEnd), + Context.getSourceManager(), Context.getLangOpts()); + Diag << tooling::fixit::createReplacement(CS->getSourceRange(), Repl); + } else { + SourceLocation ElseExpandedLoc = Remap(ElseLoc); + SourceLocation EndLoc = Remap(Else->getEndLoc()); + + llvm::StringRef Repl = Lexer::getSourceText( + CharSourceRange::getTokenRange( + ElseExpandedLoc.getLocWithOffset(TokLen(ElseLoc) + 1), EndLoc), + Context.getSourceManager(), Context.getLangOpts()); + Diag << tooling::fixit::createReplacement( + SourceRange(ElseExpandedLoc, EndLoc), Repl); + } +} + +ElseAfterReturnCheck::ElseAfterReturnCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WarnOnUnfixable(Options.get(WarnOnUnfixableStr, true)), + WarnOnConditionVariables(Options.get(WarnOnConditionVariablesStr, true)) { +} + +void ElseAfterReturnCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, WarnOnUnfixableStr, WarnOnUnfixable); + Options.store(Opts, WarnOnConditionVariablesStr, WarnOnConditionVariables); +} + +void ElseAfterReturnCheck::registerPPCallbacks(const SourceManager &SM, + Preprocessor *PP, + Preprocessor *ModuleExpanderPP) { + PP->addPPCallbacks( + std::make_unique<PPConditionalCollector>(this->PPConditionals, SM)); +} + +void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) { + const auto InterruptsControlFlow = stmt(anyOf( + returnStmt().bind(InterruptingStr), continueStmt().bind(InterruptingStr), + breakStmt().bind(InterruptingStr), cxxThrowExpr().bind(InterruptingStr))); + Finder->addMatcher( + compoundStmt( + forEach(ifStmt(unless(isConstexpr()), + hasThen(stmt( + anyOf(InterruptsControlFlow, + compoundStmt(has(InterruptsControlFlow))))), + hasElse(stmt().bind("else"))) + .bind("if"))) + .bind("cs"), + this); +} + +static bool hasPreprocessorBranchEndBetweenLocations( + const ElseAfterReturnCheck::ConditionalBranchMap &ConditionalBranchMap, + const SourceManager &SM, SourceLocation StartLoc, SourceLocation EndLoc) { + + SourceLocation ExpandedStartLoc = SM.getExpansionLoc(StartLoc); + SourceLocation ExpandedEndLoc = SM.getExpansionLoc(EndLoc); + if (!SM.isWrittenInSameFile(ExpandedStartLoc, ExpandedEndLoc)) + return false; + + // StartLoc and EndLoc expand to the same macro. + if (ExpandedStartLoc == ExpandedEndLoc) + return false; + + assert(ExpandedStartLoc < ExpandedEndLoc); + + auto Iter = ConditionalBranchMap.find(SM.getFileID(ExpandedEndLoc)); + + if (Iter == ConditionalBranchMap.end() || Iter->getSecond().empty()) + return false; + + const SmallVectorImpl<SourceRange> &ConditionalBranches = Iter->getSecond(); + + assert(llvm::is_sorted(ConditionalBranches, + [](const SourceRange &LHS, const SourceRange &RHS) { + return LHS.getEnd() < RHS.getEnd(); + })); + + // First conditional block that ends after ExpandedStartLoc. + const auto *Begin = + llvm::lower_bound(ConditionalBranches, ExpandedStartLoc, + [](const SourceRange &LHS, const SourceLocation &RHS) { + return LHS.getEnd() < RHS; + }); + const auto *End = ConditionalBranches.end(); + for (; Begin != End && Begin->getEnd() < ExpandedEndLoc; ++Begin) + if (Begin->getBegin() < ExpandedStartLoc) + return true; + return false; +} + +static StringRef getControlFlowString(const Stmt &Stmt) { + if (isa<ReturnStmt>(Stmt)) + return "return"; + if (isa<ContinueStmt>(Stmt)) + return "continue"; + if (isa<BreakStmt>(Stmt)) + return "break"; + if (isa<CXXThrowExpr>(Stmt)) + return "throw"; + llvm_unreachable("Unknown control flow interruptor"); +} + +void ElseAfterReturnCheck::check(const MatchFinder::MatchResult &Result) { + const auto *If = Result.Nodes.getNodeAs<IfStmt>("if"); + const auto *Else = Result.Nodes.getNodeAs<Stmt>("else"); + const auto *OuterScope = Result.Nodes.getNodeAs<CompoundStmt>("cs"); + const auto *Interrupt = Result.Nodes.getNodeAs<Stmt>(InterruptingStr); + SourceLocation ElseLoc = If->getElseLoc(); + + if (hasPreprocessorBranchEndBetweenLocations( + PPConditionals, *Result.SourceManager, Interrupt->getBeginLoc(), + ElseLoc)) + return; + + bool IsLastInScope = OuterScope->body_back() == If; + StringRef ControlFlowInterruptor = getControlFlowString(*Interrupt); + + if (!IsLastInScope && containsDeclInScope(Else)) { + if (WarnOnUnfixable) { + // Warn, but don't attempt an autofix. + diag(ElseLoc, WarningMessage) << ControlFlowInterruptor; + } + return; + } + + if (checkConditionVarUsageInElse(If) != nullptr) { + if (!WarnOnConditionVariables) + return; + if (IsLastInScope) { + // If the if statement is the last statement of its enclosing statements + // scope, we can pull the decl out of the if statement. + DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage) + << ControlFlowInterruptor + << SourceRange(ElseLoc); + if (checkInitDeclUsageInElse(If) != nullptr) { + Diag << tooling::fixit::createReplacement( + SourceRange(If->getIfLoc()), + (tooling::fixit::getText(*If->getInit(), *Result.Context) + + llvm::StringRef("\n")) + .str()) + << tooling::fixit::createRemoval(If->getInit()->getSourceRange()); + } + const DeclStmt *VDeclStmt = If->getConditionVariableDeclStmt(); + const VarDecl *VDecl = If->getConditionVariable(); + std::string Repl = + (tooling::fixit::getText(*VDeclStmt, *Result.Context) + + llvm::StringRef(";\n") + + tooling::fixit::getText(If->getIfLoc(), *Result.Context)) + .str(); + Diag << tooling::fixit::createReplacement(SourceRange(If->getIfLoc()), + Repl) + << tooling::fixit::createReplacement(VDeclStmt->getSourceRange(), + VDecl->getName()); + removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc); + } else if (WarnOnUnfixable) { + // Warn, but don't attempt an autofix. + diag(ElseLoc, WarningMessage) << ControlFlowInterruptor; + } + return; + } + + if (checkInitDeclUsageInElse(If) != nullptr) { + if (!WarnOnConditionVariables) + return; + if (IsLastInScope) { + // If the if statement is the last statement of its enclosing statements + // scope, we can pull the decl out of the if statement. + DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage) + << ControlFlowInterruptor + << SourceRange(ElseLoc); + Diag << tooling::fixit::createReplacement( + SourceRange(If->getIfLoc()), + (tooling::fixit::getText(*If->getInit(), *Result.Context) + + "\n" + + tooling::fixit::getText(If->getIfLoc(), *Result.Context)) + .str()) + << tooling::fixit::createRemoval(If->getInit()->getSourceRange()); + removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc); + } else if (WarnOnUnfixable) { + // Warn, but don't attempt an autofix. + diag(ElseLoc, WarningMessage) << ControlFlowInterruptor; + } + return; + } + + DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage) + << ControlFlowInterruptor << SourceRange(ElseLoc); + removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ElseAfterReturnCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ElseAfterReturnCheck.h new file mode 100644 index 0000000000..34860c2853 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ElseAfterReturnCheck.h @@ -0,0 +1,44 @@ +//===--- ElseAfterReturnCheck.h - clang-tidy---------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ELSEAFTERRETURNCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ELSEAFTERRETURNCHECK_H + +#include "../ClangTidyCheck.h" +#include "llvm/ADT/DenseMap.h" + +namespace clang::tidy::readability { + +/// Flags the usages of `else` after `return`. +/// +/// http://llvm.org/docs/CodingStandards.html#don-t-use-else-after-a-return +class ElseAfterReturnCheck : public ClangTidyCheck { +public: + ElseAfterReturnCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + + using ConditionalBranchMap = + llvm::DenseMap<FileID, SmallVector<SourceRange, 1>>; + +private: + const bool WarnOnUnfixable; + const bool WarnOnConditionVariables; + ConditionalBranchMap PPConditionals; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ELSEAFTERRETURNCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp new file mode 100644 index 0000000000..b5dc09c5f1 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.cpp @@ -0,0 +1,563 @@ +//===--- FunctionCognitiveComplexityCheck.cpp - clang-tidy ------*- C++ -*-===// +// +// 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 "FunctionCognitiveComplexityCheck.h" +#include "../ClangTidyDiagnosticConsumer.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" +#include "clang/AST/Expr.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/Stmt.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/ASTMatchersInternal.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticIDs.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceLocation.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" +#include <array> +#include <cassert> +#include <optional> +#include <stack> +#include <tuple> +#include <type_traits> +#include <utility> + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { +namespace { + +struct CognitiveComplexity final { + // Any increment is based on some combination of reasons. + // For details you can look at the Specification at + // https://www.sonarsource.com/docs/CognitiveComplexity.pdf + // or user-facing docs at + // http://clang.llvm.org/extra/clang-tidy/checks/readability/function-cognitive-complexity.html + // Here are all the possible reasons: + enum Criteria : uint8_t { + None = 0U, + + // B1, increases cognitive complexity (by 1) + // What causes it: + // * if, else if, else, ConditionalOperator (not BinaryConditionalOperator) + // * SwitchStmt + // * ForStmt, CXXForRangeStmt + // * WhileStmt, DoStmt + // * CXXCatchStmt + // * GotoStmt, IndirectGotoStmt (but not BreakStmt, ContinueStmt) + // * sequences of binary logical operators (BinOpLAnd, BinOpLOr) + // * each method in a recursion cycle (not implemented) + Increment = 1U << 0, + + // B2, increases current nesting level (by 1) + // What causes it: + // * if, else if, else, ConditionalOperator (not BinaryConditionalOperator) + // * SwitchStmt + // * ForStmt, CXXForRangeStmt + // * WhileStmt, DoStmt + // * CXXCatchStmt + // * nested CXXConstructor, CXXDestructor, CXXMethod (incl. C++11 Lambda) + // * GNU Statement Expression + // * Apple Block declaration + IncrementNesting = 1U << 1, + + // B3, increases cognitive complexity by the current nesting level + // Applied before IncrementNesting + // What causes it: + // * IfStmt, ConditionalOperator (not BinaryConditionalOperator) + // * SwitchStmt + // * ForStmt, CXXForRangeStmt + // * WhileStmt, DoStmt + // * CXXCatchStmt + PenalizeNesting = 1U << 2, + + All = Increment | PenalizeNesting | IncrementNesting, + }; + + // The helper struct used to record one increment occurrence, with all the + // details necessary. + struct Detail { + const SourceLocation Loc; // What caused the increment? + const unsigned short Nesting; // How deeply nested is Loc located? + const Criteria C; // The criteria of the increment + + Detail(SourceLocation SLoc, unsigned short CurrentNesting, Criteria Crit) + : Loc(SLoc), Nesting(CurrentNesting), C(Crit) {} + + // To minimize the sizeof(Detail), we only store the minimal info there. + // This function is used to convert from the stored info into the usable + // information - what message to output, how much of an increment did this + // occurrence actually result in. + std::pair<unsigned, unsigned short> process() const { + assert(C != Criteria::None && "invalid criteria"); + + unsigned MsgId; // The id of the message to output. + unsigned short Increment; // How much of an increment? + + if (C == Criteria::All) { + Increment = 1 + Nesting; + MsgId = 0; + } else if (C == (Criteria::Increment | Criteria::IncrementNesting)) { + Increment = 1; + MsgId = 1; + } else if (C == Criteria::Increment) { + Increment = 1; + MsgId = 2; + } else if (C == Criteria::IncrementNesting) { + Increment = 0; // Unused in this message. + MsgId = 3; + } else + llvm_unreachable("should not get to here."); + + return std::make_pair(MsgId, Increment); + } + }; + + // Limit of 25 is the "upstream"'s default. + static constexpr unsigned DefaultLimit = 25U; + + // Based on the publicly-avaliable numbers for some big open-source projects + // https://sonarcloud.io/projects?languages=c%2Ccpp&size=5 we can estimate: + // value ~20 would result in no allocs for 98% of functions, ~12 for 96%, ~10 + // for 91%, ~8 for 88%, ~6 for 84%, ~4 for 77%, ~2 for 64%, and ~1 for 37%. + static_assert(sizeof(Detail) <= 8, + "Since we use SmallVector to minimize the amount of " + "allocations, we also need to consider the price we pay for " + "that in terms of stack usage. " + "Thus, it is good to minimize the size of the Detail struct."); + SmallVector<Detail, DefaultLimit> Details; // 25 elements is 200 bytes. + // Yes, 25 is a magic number. This is the seemingly-sane default for the + // upper limit for function cognitive complexity. Thus it would make sense + // to avoid allocations for any function that does not violate the limit. + + // The grand total Cognitive Complexity of the function. + unsigned Total = 0; + + // The function used to store new increment, calculate the total complexity. + void account(SourceLocation Loc, unsigned short Nesting, Criteria C); +}; + +// All the possible messages that can be output. The choice of the message +// to use is based of the combination of the CognitiveComplexity::Criteria. +// It would be nice to have it in CognitiveComplexity struct, but then it is +// not static. +static const std::array<const StringRef, 4> Msgs = {{ + // B1 + B2 + B3 + "+%0, including nesting penalty of %1, nesting level increased to %2", + + // B1 + B2 + "+%0, nesting level increased to %2", + + // B1 + "+%0", + + // B2 + "nesting level increased to %2", +}}; + +// Criteria is a bitset, thus a few helpers are needed. +CognitiveComplexity::Criteria operator|(CognitiveComplexity::Criteria LHS, + CognitiveComplexity::Criteria RHS) { + return static_cast<CognitiveComplexity::Criteria>( + static_cast<std::underlying_type_t<CognitiveComplexity::Criteria>>(LHS) | + static_cast<std::underlying_type_t<CognitiveComplexity::Criteria>>(RHS)); +} +CognitiveComplexity::Criteria operator&(CognitiveComplexity::Criteria LHS, + CognitiveComplexity::Criteria RHS) { + return static_cast<CognitiveComplexity::Criteria>( + static_cast<std::underlying_type_t<CognitiveComplexity::Criteria>>(LHS) & + static_cast<std::underlying_type_t<CognitiveComplexity::Criteria>>(RHS)); +} +CognitiveComplexity::Criteria &operator|=(CognitiveComplexity::Criteria &LHS, + CognitiveComplexity::Criteria RHS) { + LHS = operator|(LHS, RHS); + return LHS; +} +CognitiveComplexity::Criteria &operator&=(CognitiveComplexity::Criteria &LHS, + CognitiveComplexity::Criteria RHS) { + LHS = operator&(LHS, RHS); + return LHS; +} + +void CognitiveComplexity::account(SourceLocation Loc, unsigned short Nesting, + Criteria C) { + C &= Criteria::All; + assert(C != Criteria::None && "invalid criteria"); + + Details.emplace_back(Loc, Nesting, C); + const Detail &D = Details.back(); + + unsigned MsgId; + unsigned short Increase; + std::tie(MsgId, Increase) = D.process(); + + Total += Increase; +} + +class FunctionASTVisitor final + : public RecursiveASTVisitor<FunctionASTVisitor> { + using Base = RecursiveASTVisitor<FunctionASTVisitor>; + + // If set to true, macros are ignored during analysis. + const bool IgnoreMacros; + + // The current nesting level (increased by Criteria::IncrementNesting). + unsigned short CurrentNestingLevel = 0; + + // Used to efficiently know the last type of the binary sequence operator + // that was encountered. It would make sense for the function call to start + // the new sequence, thus it is a stack. + using OBO = std::optional<BinaryOperator::Opcode>; + std::stack<OBO, SmallVector<OBO, 4>> BinaryOperatorsStack; + +public: + explicit FunctionASTVisitor(const bool IgnoreMacros) + : IgnoreMacros(IgnoreMacros) {} + + bool traverseStmtWithIncreasedNestingLevel(Stmt *Node) { + ++CurrentNestingLevel; + bool ShouldContinue = Base::TraverseStmt(Node); + --CurrentNestingLevel; + return ShouldContinue; + } + + bool traverseDeclWithIncreasedNestingLevel(Decl *Node) { + ++CurrentNestingLevel; + bool ShouldContinue = Base::TraverseDecl(Node); + --CurrentNestingLevel; + return ShouldContinue; + } + + bool TraverseIfStmt(IfStmt *Node, bool InElseIf = false) { + if (!Node) + return Base::TraverseIfStmt(Node); + + { + CognitiveComplexity::Criteria Reasons; + + Reasons = CognitiveComplexity::Criteria::None; + + // "If" increases cognitive complexity. + Reasons |= CognitiveComplexity::Criteria::Increment; + // "If" increases nesting level. + Reasons |= CognitiveComplexity::Criteria::IncrementNesting; + + if (!InElseIf) { + // "If" receives a nesting increment commensurate with it's nested + // depth, if it is not part of "else if". + Reasons |= CognitiveComplexity::Criteria::PenalizeNesting; + } + + CC.account(Node->getIfLoc(), CurrentNestingLevel, Reasons); + } + + // If this IfStmt is *NOT* "else if", then only the body (i.e. "Then" and + // "Else") is traversed with increased Nesting level. + // However if this IfStmt *IS* "else if", then Nesting level is increased + // for the whole IfStmt (i.e. for "Init", "Cond", "Then" and "Else"). + + if (!InElseIf) { + if (!TraverseStmt(Node->getInit())) + return false; + + if (!TraverseStmt(Node->getCond())) + return false; + } else { + if (!traverseStmtWithIncreasedNestingLevel(Node->getInit())) + return false; + + if (!traverseStmtWithIncreasedNestingLevel(Node->getCond())) + return false; + } + + // "Then" always increases nesting level. + if (!traverseStmtWithIncreasedNestingLevel(Node->getThen())) + return false; + + if (!Node->getElse()) + return true; + + if (auto *E = dyn_cast<IfStmt>(Node->getElse())) + return TraverseIfStmt(E, true); + + { + CognitiveComplexity::Criteria Reasons; + + Reasons = CognitiveComplexity::Criteria::None; + + // "Else" increases cognitive complexity. + Reasons |= CognitiveComplexity::Criteria::Increment; + // "Else" increases nesting level. + Reasons |= CognitiveComplexity::Criteria::IncrementNesting; + // "Else" DOES NOT receive a nesting increment commensurate with it's + // nested depth. + + CC.account(Node->getElseLoc(), CurrentNestingLevel, Reasons); + } + + // "Else" always increases nesting level. + return traverseStmtWithIncreasedNestingLevel(Node->getElse()); + } + +// The currently-being-processed stack entry, which is always the top. +#define CurrentBinaryOperator BinaryOperatorsStack.top() + + // In a sequence of binary logical operators, if the new operator is different + // from the previous one, then the cognitive complexity is increased. + bool TraverseBinaryOperator(BinaryOperator *Op) { + if (!Op || !Op->isLogicalOp()) + return Base::TraverseBinaryOperator(Op); + + // Make sure that there is always at least one frame in the stack. + if (BinaryOperatorsStack.empty()) + BinaryOperatorsStack.emplace(); + + // If this is the first binary operator that we are processing, or the + // previous binary operator was different, there is an increment. + if (!CurrentBinaryOperator || Op->getOpcode() != CurrentBinaryOperator) + CC.account(Op->getOperatorLoc(), CurrentNestingLevel, + CognitiveComplexity::Criteria::Increment); + + // We might encounter a function call, which starts a new sequence, thus + // we need to save the current previous binary operator. + const std::optional<BinaryOperator::Opcode> BinOpCopy( + CurrentBinaryOperator); + + // Record the operator that we are currently processing and traverse it. + CurrentBinaryOperator = Op->getOpcode(); + bool ShouldContinue = Base::TraverseBinaryOperator(Op); + + // And restore the previous binary operator, which might be nonexistent. + CurrentBinaryOperator = BinOpCopy; + + return ShouldContinue; + } + + // It would make sense for the function call to start the new binary + // operator sequence, thus let's make sure that it creates a new stack frame. + bool TraverseCallExpr(CallExpr *Node) { + // If we are not currently processing any binary operator sequence, then + // no Node-handling is needed. + if (!Node || BinaryOperatorsStack.empty() || !CurrentBinaryOperator) + return Base::TraverseCallExpr(Node); + + // Else, do add [uninitialized] frame to the stack, and traverse call. + BinaryOperatorsStack.emplace(); + bool ShouldContinue = Base::TraverseCallExpr(Node); + // And remove the top frame. + BinaryOperatorsStack.pop(); + + return ShouldContinue; + } + +#undef CurrentBinaryOperator + + bool TraverseStmt(Stmt *Node) { + if (!Node) + return Base::TraverseStmt(Node); + + if (IgnoreMacros && Node->getBeginLoc().isMacroID()) + return true; + + // Three following switch()'es have huge duplication, but it is better to + // keep them separate, to simplify comparing them with the Specification. + + CognitiveComplexity::Criteria Reasons = CognitiveComplexity::Criteria::None; + SourceLocation Location = Node->getBeginLoc(); + + // B1. Increments + // There is an increment for each of the following: + switch (Node->getStmtClass()) { + // if, else if, else are handled in TraverseIfStmt(), + // FIXME: "each method in a recursion cycle" Increment is not implemented. + case Stmt::ConditionalOperatorClass: + case Stmt::SwitchStmtClass: + case Stmt::ForStmtClass: + case Stmt::CXXForRangeStmtClass: + case Stmt::WhileStmtClass: + case Stmt::DoStmtClass: + case Stmt::CXXCatchStmtClass: + case Stmt::GotoStmtClass: + case Stmt::IndirectGotoStmtClass: + Reasons |= CognitiveComplexity::Criteria::Increment; + break; + default: + // break LABEL, continue LABEL increase cognitive complexity, + // but they are not supported in C++ or C. + // Regular break/continue do not increase cognitive complexity. + break; + } + + // B2. Nesting level + // The following structures increment the nesting level: + switch (Node->getStmtClass()) { + // if, else if, else are handled in TraverseIfStmt(), + // Nested methods and such are handled in TraverseDecl. + case Stmt::ConditionalOperatorClass: + case Stmt::SwitchStmtClass: + case Stmt::ForStmtClass: + case Stmt::CXXForRangeStmtClass: + case Stmt::WhileStmtClass: + case Stmt::DoStmtClass: + case Stmt::CXXCatchStmtClass: + case Stmt::LambdaExprClass: + case Stmt::StmtExprClass: + Reasons |= CognitiveComplexity::Criteria::IncrementNesting; + break; + default: + break; + } + + // B3. Nesting increments + // The following structures receive a nesting increment + // commensurate with their nested depth inside B2 structures: + switch (Node->getStmtClass()) { + // if, else if, else are handled in TraverseIfStmt(). + case Stmt::ConditionalOperatorClass: + case Stmt::SwitchStmtClass: + case Stmt::ForStmtClass: + case Stmt::CXXForRangeStmtClass: + case Stmt::WhileStmtClass: + case Stmt::DoStmtClass: + case Stmt::CXXCatchStmtClass: + Reasons |= CognitiveComplexity::Criteria::PenalizeNesting; + break; + default: + break; + } + + if (Node->getStmtClass() == Stmt::ConditionalOperatorClass) { + // A little beautification. + // For conditional operator "cond ? true : false" point at the "?" + // symbol. + Location = cast<ConditionalOperator>(Node)->getQuestionLoc(); + } + + // If we have found any reasons, let's account it. + if (Reasons & CognitiveComplexity::Criteria::All) + CC.account(Location, CurrentNestingLevel, Reasons); + + // Did we decide that the nesting level should be increased? + if (!(Reasons & CognitiveComplexity::Criteria::IncrementNesting)) + return Base::TraverseStmt(Node); + + return traverseStmtWithIncreasedNestingLevel(Node); + } + + // The parameter MainAnalyzedFunction is needed to differentiate between the + // cases where TraverseDecl() is the entry point from + // FunctionCognitiveComplexityCheck::check() and the cases where it was called + // from the FunctionASTVisitor itself. Explanation: if we get a function + // definition (e.g. constructor, destructor, method), the Cognitive Complexity + // specification states that the Nesting level shall be increased. But if this + // function is the entry point, then the Nesting level should not be + // increased. Thus that parameter is there and is used to fall-through + // directly to traversing if this is the main function that is being analyzed. + bool TraverseDecl(Decl *Node, bool MainAnalyzedFunction = false) { + if (!Node || MainAnalyzedFunction) + return Base::TraverseDecl(Node); + + // B2. Nesting level + // The following structures increment the nesting level: + switch (Node->getKind()) { + case Decl::Function: + case Decl::CXXMethod: + case Decl::CXXConstructor: + case Decl::CXXDestructor: + case Decl::Block: + break; + default: + // If this is something else, we use early return! + return Base::TraverseDecl(Node); + break; + } + + CC.account(Node->getBeginLoc(), CurrentNestingLevel, + CognitiveComplexity::Criteria::IncrementNesting); + + return traverseDeclWithIncreasedNestingLevel(Node); + } + + CognitiveComplexity CC; +}; + +} // namespace + +FunctionCognitiveComplexityCheck::FunctionCognitiveComplexityCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + Threshold(Options.get("Threshold", CognitiveComplexity::DefaultLimit)), + DescribeBasicIncrements(Options.get("DescribeBasicIncrements", true)), + IgnoreMacros(Options.get("IgnoreMacros", false)) {} + +void FunctionCognitiveComplexityCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "Threshold", Threshold); + Options.store(Opts, "DescribeBasicIncrements", DescribeBasicIncrements); + Options.store(Opts, "IgnoreMacros", IgnoreMacros); +} + +void FunctionCognitiveComplexityCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + functionDecl(isDefinition(), + unless(anyOf(isDefaulted(), isDeleted(), isWeak()))) + .bind("func"), + this); + Finder->addMatcher(lambdaExpr().bind("lambda"), this); +} + +void FunctionCognitiveComplexityCheck::check( + const MatchFinder::MatchResult &Result) { + + FunctionASTVisitor Visitor(IgnoreMacros); + SourceLocation Loc; + + const auto *TheDecl = Result.Nodes.getNodeAs<FunctionDecl>("func"); + const auto *TheLambdaExpr = Result.Nodes.getNodeAs<LambdaExpr>("lambda"); + if (TheDecl) { + assert(TheDecl->hasBody() && + "The matchers should only match the functions that " + "have user-provided body."); + Loc = TheDecl->getLocation(); + Visitor.TraverseDecl(const_cast<FunctionDecl *>(TheDecl), true); + } else { + Loc = TheLambdaExpr->getBeginLoc(); + Visitor.TraverseLambdaExpr(const_cast<LambdaExpr *>(TheLambdaExpr)); + } + + if (Visitor.CC.Total <= Threshold) + return; + + if (TheDecl) + diag(Loc, "function %0 has cognitive complexity of %1 (threshold %2)") + << TheDecl << Visitor.CC.Total << Threshold; + else + diag(Loc, "lambda has cognitive complexity of %0 (threshold %1)") + << Visitor.CC.Total << Threshold; + + if (!DescribeBasicIncrements) + return; + + // Output all the basic increments of complexity. + for (const auto &Detail : Visitor.CC.Details) { + unsigned MsgId; // The id of the message to output. + unsigned short Increase; // How much of an increment? + std::tie(MsgId, Increase) = Detail.process(); + assert(MsgId < Msgs.size() && "MsgId should always be valid"); + // Increase, on the other hand, can be 0. + + diag(Detail.Loc, Msgs[MsgId], DiagnosticIDs::Note) + << (unsigned)Increase << (unsigned)Detail.Nesting << 1 + Detail.Nesting; + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.h new file mode 100644 index 0000000000..bdb8550eea --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/FunctionCognitiveComplexityCheck.h @@ -0,0 +1,51 @@ +//===--- FunctionCognitiveComplexityCheck.h - clang-tidy --------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FUNCTIONCOGNITIVECOMPLEXITYCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FUNCTIONCOGNITIVECOMPLEXITYCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Checks function Cognitive Complexity metric. +/// +/// There are the following configuration option: +/// +/// * `Threshold` - flag functions with Cognitive Complexity exceeding +/// this number. The default is `25`. +/// * `DescribeBasicIncrements`- if set to `true`, then for each function +/// exceeding the complexity threshold the check will issue additional +/// diagnostics on every piece of code (loop, `if` statement, etc.) which +/// contributes to that complexity. +// Default is `true` +/// * `IgnoreMacros` - if set to `true`, the check will ignore code inside +/// macros. Default is `false`. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/function-cognitive-complexity.html +class FunctionCognitiveComplexityCheck : public ClangTidyCheck { +public: + FunctionCognitiveComplexityCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + const unsigned Threshold; + const bool DescribeBasicIncrements; + const bool IgnoreMacros; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FUNCTIONCOGNITIVECOMPLEXITYCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/FunctionSizeCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/FunctionSizeCheck.cpp new file mode 100644 index 0000000000..3c803959ca --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/FunctionSizeCheck.cpp @@ -0,0 +1,222 @@ +//===-- FunctionSizeCheck.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 "FunctionSizeCheck.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/BitVector.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { +namespace { + +class FunctionASTVisitor : public RecursiveASTVisitor<FunctionASTVisitor> { + using Base = RecursiveASTVisitor<FunctionASTVisitor>; + +public: + bool VisitVarDecl(VarDecl *VD) { + // Do not count function params. + // Do not count decomposition declarations (C++17's structured bindings). + if (StructNesting == 0 && + !(isa<ParmVarDecl>(VD) || isa<DecompositionDecl>(VD))) + ++Info.Variables; + return true; + } + bool VisitBindingDecl(BindingDecl *BD) { + // Do count each of the bindings (in the decomposition declaration). + if (StructNesting == 0) + ++Info.Variables; + return true; + } + + bool TraverseStmt(Stmt *Node) { + if (!Node) + return Base::TraverseStmt(Node); + + if (TrackedParent.back() && !isa<CompoundStmt>(Node)) + ++Info.Statements; + + switch (Node->getStmtClass()) { + case Stmt::IfStmtClass: + case Stmt::WhileStmtClass: + case Stmt::DoStmtClass: + case Stmt::CXXForRangeStmtClass: + case Stmt::ForStmtClass: + case Stmt::SwitchStmtClass: + ++Info.Branches; + [[fallthrough]]; + case Stmt::CompoundStmtClass: + TrackedParent.push_back(true); + break; + default: + TrackedParent.push_back(false); + break; + } + + Base::TraverseStmt(Node); + + TrackedParent.pop_back(); + + return true; + } + + bool TraverseCompoundStmt(CompoundStmt *Node) { + // If this new compound statement is located in a compound statement, which + // is already nested NestingThreshold levels deep, record the start location + // of this new compound statement. + if (CurrentNestingLevel == Info.NestingThreshold) + Info.NestingThresholders.push_back(Node->getBeginLoc()); + + ++CurrentNestingLevel; + Base::TraverseCompoundStmt(Node); + --CurrentNestingLevel; + + return true; + } + + bool TraverseDecl(Decl *Node) { + TrackedParent.push_back(false); + Base::TraverseDecl(Node); + TrackedParent.pop_back(); + return true; + } + + bool TraverseLambdaExpr(LambdaExpr *Node) { + ++StructNesting; + Base::TraverseLambdaExpr(Node); + --StructNesting; + return true; + } + + bool TraverseCXXRecordDecl(CXXRecordDecl *Node) { + ++StructNesting; + Base::TraverseCXXRecordDecl(Node); + --StructNesting; + return true; + } + + bool TraverseStmtExpr(StmtExpr *SE) { + ++StructNesting; + Base::TraverseStmtExpr(SE); + --StructNesting; + return true; + } + + struct FunctionInfo { + unsigned Lines = 0; + unsigned Statements = 0; + unsigned Branches = 0; + unsigned NestingThreshold = 0; + unsigned Variables = 0; + std::vector<SourceLocation> NestingThresholders; + }; + FunctionInfo Info; + llvm::BitVector TrackedParent; + unsigned StructNesting = 0; + unsigned CurrentNestingLevel = 0; +}; + +} // namespace + +FunctionSizeCheck::FunctionSizeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + LineThreshold(Options.get("LineThreshold", -1U)), + StatementThreshold(Options.get("StatementThreshold", 800U)), + BranchThreshold(Options.get("BranchThreshold", -1U)), + ParameterThreshold(Options.get("ParameterThreshold", -1U)), + NestingThreshold(Options.get("NestingThreshold", -1U)), + VariableThreshold(Options.get("VariableThreshold", -1U)) {} + +void FunctionSizeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "LineThreshold", LineThreshold); + Options.store(Opts, "StatementThreshold", StatementThreshold); + Options.store(Opts, "BranchThreshold", BranchThreshold); + Options.store(Opts, "ParameterThreshold", ParameterThreshold); + Options.store(Opts, "NestingThreshold", NestingThreshold); + Options.store(Opts, "VariableThreshold", VariableThreshold); +} + +void FunctionSizeCheck::registerMatchers(MatchFinder *Finder) { + // Lambdas ignored - historically considered part of enclosing function. + // FIXME: include them instead? Top-level lambdas are currently never counted. + Finder->addMatcher(functionDecl(unless(isInstantiated()), + unless(cxxMethodDecl(ofClass(isLambda())))) + .bind("func"), + this); +} + +void FunctionSizeCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func"); + + FunctionASTVisitor Visitor; + Visitor.Info.NestingThreshold = NestingThreshold; + Visitor.TraverseDecl(const_cast<FunctionDecl *>(Func)); + auto &FI = Visitor.Info; + + if (FI.Statements == 0) + return; + + // Count the lines including whitespace and comments. Really simple. + if (const Stmt *Body = Func->getBody()) { + SourceManager *SM = Result.SourceManager; + if (SM->isWrittenInSameFile(Body->getBeginLoc(), Body->getEndLoc())) { + FI.Lines = SM->getSpellingLineNumber(Body->getEndLoc()) - + SM->getSpellingLineNumber(Body->getBeginLoc()); + } + } + + unsigned ActualNumberParameters = Func->getNumParams(); + + if (FI.Lines > LineThreshold || FI.Statements > StatementThreshold || + FI.Branches > BranchThreshold || + ActualNumberParameters > ParameterThreshold || + !FI.NestingThresholders.empty() || FI.Variables > VariableThreshold) { + diag(Func->getLocation(), + "function %0 exceeds recommended size/complexity thresholds") + << Func; + } + + if (FI.Lines > LineThreshold) { + diag(Func->getLocation(), + "%0 lines including whitespace and comments (threshold %1)", + DiagnosticIDs::Note) + << FI.Lines << LineThreshold; + } + + if (FI.Statements > StatementThreshold) { + diag(Func->getLocation(), "%0 statements (threshold %1)", + DiagnosticIDs::Note) + << FI.Statements << StatementThreshold; + } + + if (FI.Branches > BranchThreshold) { + diag(Func->getLocation(), "%0 branches (threshold %1)", DiagnosticIDs::Note) + << FI.Branches << BranchThreshold; + } + + if (ActualNumberParameters > ParameterThreshold) { + diag(Func->getLocation(), "%0 parameters (threshold %1)", + DiagnosticIDs::Note) + << ActualNumberParameters << ParameterThreshold; + } + + for (const auto &CSPos : FI.NestingThresholders) { + diag(CSPos, "nesting level %0 starts here (threshold %1)", + DiagnosticIDs::Note) + << NestingThreshold + 1 << NestingThreshold; + } + + if (FI.Variables > VariableThreshold) { + diag(Func->getLocation(), "%0 variables (threshold %1)", + DiagnosticIDs::Note) + << FI.Variables << VariableThreshold; + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/FunctionSizeCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/FunctionSizeCheck.h new file mode 100644 index 0000000000..5f3fb9a35d --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/FunctionSizeCheck.h @@ -0,0 +1,54 @@ +//===--- FunctionSizeCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FUNCTIONSIZECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FUNCTIONSIZECHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Checks for large functions based on various metrics. +/// +/// These options are supported: +/// +/// * `LineThreshold` - flag functions exceeding this number of lines. The +/// default is `-1` (ignore the number of lines). +/// * `StatementThreshold` - flag functions exceeding this number of +/// statements. This may differ significantly from the number of lines for +/// macro-heavy code. The default is `800`. +/// * `BranchThreshold` - flag functions exceeding this number of control +/// statements. The default is `-1` (ignore the number of branches). +/// * `ParameterThreshold` - flag functions having a high number of +/// parameters. The default is `-1` (ignore the number of parameters). +/// * `NestingThreshold` - flag compound statements which create next nesting +/// level after `NestingThreshold`. This may differ significantly from the +/// expected value for macro-heavy code. The default is `-1` (ignore the +/// nesting level). +/// * `VariableThreshold` - flag functions having a high number of variable +/// declarations. The default is `-1` (ignore the number of variables). +class FunctionSizeCheck : public ClangTidyCheck { +public: + FunctionSizeCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const unsigned LineThreshold; + const unsigned StatementThreshold; + const unsigned BranchThreshold; + const unsigned ParameterThreshold; + const unsigned NestingThreshold; + const unsigned VariableThreshold; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FUNCTIONSIZECHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/IdentifierLengthCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/IdentifierLengthCheck.cpp new file mode 100644 index 0000000000..68da28b7f1 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/IdentifierLengthCheck.cpp @@ -0,0 +1,152 @@ +//===--- IdentifierLengthCheck.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 "IdentifierLengthCheck.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +const unsigned DefaultMinimumVariableNameLength = 3; +const unsigned DefaultMinimumLoopCounterNameLength = 2; +const unsigned DefaultMinimumExceptionNameLength = 2; +const unsigned DefaultMinimumParameterNameLength = 3; +const char DefaultIgnoredLoopCounterNames[] = "^[ijk_]$"; +const char DefaultIgnoredVariableNames[] = ""; +const char DefaultIgnoredExceptionVariableNames[] = "^[e]$"; +const char DefaultIgnoredParameterNames[] = "^[n]$"; + +const char ErrorMessage[] = + "%select{variable|exception variable|loop variable|" + "parameter}0 name %1 is too short, expected at least %2 characters"; + +IdentifierLengthCheck::IdentifierLengthCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + MinimumVariableNameLength(Options.get("MinimumVariableNameLength", + DefaultMinimumVariableNameLength)), + MinimumLoopCounterNameLength(Options.get( + "MinimumLoopCounterNameLength", DefaultMinimumLoopCounterNameLength)), + MinimumExceptionNameLength(Options.get( + "MinimumExceptionNameLength", DefaultMinimumExceptionNameLength)), + MinimumParameterNameLength(Options.get( + "MinimumParameterNameLength", DefaultMinimumParameterNameLength)), + IgnoredVariableNamesInput( + Options.get("IgnoredVariableNames", DefaultIgnoredVariableNames)), + IgnoredVariableNames(IgnoredVariableNamesInput), + IgnoredLoopCounterNamesInput(Options.get("IgnoredLoopCounterNames", + DefaultIgnoredLoopCounterNames)), + IgnoredLoopCounterNames(IgnoredLoopCounterNamesInput), + IgnoredExceptionVariableNamesInput( + Options.get("IgnoredExceptionVariableNames", + DefaultIgnoredExceptionVariableNames)), + IgnoredExceptionVariableNames(IgnoredExceptionVariableNamesInput), + IgnoredParameterNamesInput( + Options.get("IgnoredParameterNames", DefaultIgnoredParameterNames)), + IgnoredParameterNames(IgnoredParameterNamesInput) {} + +void IdentifierLengthCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "MinimumVariableNameLength", MinimumVariableNameLength); + Options.store(Opts, "MinimumLoopCounterNameLength", + MinimumLoopCounterNameLength); + Options.store(Opts, "MinimumExceptionNameLength", MinimumExceptionNameLength); + Options.store(Opts, "MinimumParameterNameLength", MinimumParameterNameLength); + Options.store(Opts, "IgnoredLoopCounterNames", IgnoredLoopCounterNamesInput); + Options.store(Opts, "IgnoredVariableNames", IgnoredVariableNamesInput); + Options.store(Opts, "IgnoredExceptionVariableNames", + IgnoredExceptionVariableNamesInput); + Options.store(Opts, "IgnoredParameterNames", IgnoredParameterNamesInput); +} + +void IdentifierLengthCheck::registerMatchers(MatchFinder *Finder) { + if (MinimumLoopCounterNameLength > 1) + Finder->addMatcher( + forStmt(hasLoopInit(declStmt(forEach(varDecl().bind("loopVar"))))), + this); + + if (MinimumExceptionNameLength > 1) + Finder->addMatcher(varDecl(hasParent(cxxCatchStmt())).bind("exceptionVar"), + this); + + if (MinimumParameterNameLength > 1) + Finder->addMatcher(parmVarDecl().bind("paramVar"), this); + + if (MinimumVariableNameLength > 1) + Finder->addMatcher( + varDecl(unless(anyOf(hasParent(declStmt(hasParent(forStmt()))), + hasParent(cxxCatchStmt()), parmVarDecl()))) + .bind("standaloneVar"), + this); +} + +void IdentifierLengthCheck::check(const MatchFinder::MatchResult &Result) { + const auto *StandaloneVar = Result.Nodes.getNodeAs<VarDecl>("standaloneVar"); + if (StandaloneVar) { + if (!StandaloneVar->getIdentifier()) + return; + + StringRef VarName = StandaloneVar->getName(); + + if (VarName.size() >= MinimumVariableNameLength || + IgnoredVariableNames.match(VarName)) + return; + + diag(StandaloneVar->getLocation(), ErrorMessage) + << 0 << StandaloneVar << MinimumVariableNameLength; + } + + auto *ExceptionVarName = Result.Nodes.getNodeAs<VarDecl>("exceptionVar"); + if (ExceptionVarName) { + if (!ExceptionVarName->getIdentifier()) + return; + + StringRef VarName = ExceptionVarName->getName(); + if (VarName.size() >= MinimumExceptionNameLength || + IgnoredExceptionVariableNames.match(VarName)) + return; + + diag(ExceptionVarName->getLocation(), ErrorMessage) + << 1 << ExceptionVarName << MinimumExceptionNameLength; + } + + const auto *LoopVar = Result.Nodes.getNodeAs<VarDecl>("loopVar"); + if (LoopVar) { + if (!LoopVar->getIdentifier()) + return; + + StringRef VarName = LoopVar->getName(); + + if (VarName.size() >= MinimumLoopCounterNameLength || + IgnoredLoopCounterNames.match(VarName)) + return; + + diag(LoopVar->getLocation(), ErrorMessage) + << 2 << LoopVar << MinimumLoopCounterNameLength; + } + + const auto *ParamVar = Result.Nodes.getNodeAs<VarDecl>("paramVar"); + if (ParamVar) { + if (!ParamVar->getIdentifier()) + return; + + StringRef VarName = ParamVar->getName(); + + if (VarName.size() >= MinimumParameterNameLength || + IgnoredParameterNames.match(VarName)) + return; + + diag(ParamVar->getLocation(), ErrorMessage) + << 3 << ParamVar << MinimumParameterNameLength; + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/IdentifierLengthCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/IdentifierLengthCheck.h new file mode 100644 index 0000000000..2a4b810264 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/IdentifierLengthCheck.h @@ -0,0 +1,50 @@ +//===--- IdentifierLengthCheck.h - clang-tidy ---------------------*- C++ +//-*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERLENGTHCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERLENGTHCHECK_H + +#include "../ClangTidyCheck.h" +#include "llvm/Support/Regex.h" + +namespace clang::tidy::readability { + +/// Warns about identifiers names whose length is too short. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/identifier-length.html +class IdentifierLengthCheck : public ClangTidyCheck { +public: + IdentifierLengthCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const unsigned MinimumVariableNameLength; + const unsigned MinimumLoopCounterNameLength; + const unsigned MinimumExceptionNameLength; + const unsigned MinimumParameterNameLength; + + std::string IgnoredVariableNamesInput; + llvm::Regex IgnoredVariableNames; + + std::string IgnoredLoopCounterNamesInput; + llvm::Regex IgnoredLoopCounterNames; + + std::string IgnoredExceptionVariableNamesInput; + llvm::Regex IgnoredExceptionVariableNames; + + std::string IgnoredParameterNamesInput; + llvm::Regex IgnoredParameterNames; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERLENGTHCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/IdentifierNamingCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/IdentifierNamingCheck.cpp new file mode 100644 index 0000000000..b2b2875731 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/IdentifierNamingCheck.cpp @@ -0,0 +1,1453 @@ +//===--- IdentifierNamingCheck.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 "IdentifierNamingCheck.h" + +#include "../GlobList.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Regex.h" +#include "llvm/Support/YAMLParser.h" +#include <optional> + +#define DEBUG_TYPE "clang-tidy" + +// FixItHint + +using namespace clang::ast_matchers; + +namespace clang::tidy { + +llvm::ArrayRef< + std::pair<readability::IdentifierNamingCheck::CaseType, StringRef>> +OptionEnumMapping< + readability::IdentifierNamingCheck::CaseType>::getEnumMapping() { + static constexpr std::pair<readability::IdentifierNamingCheck::CaseType, + StringRef> + Mapping[] = { + {readability::IdentifierNamingCheck::CT_AnyCase, "aNy_CasE"}, + {readability::IdentifierNamingCheck::CT_LowerCase, "lower_case"}, + {readability::IdentifierNamingCheck::CT_UpperCase, "UPPER_CASE"}, + {readability::IdentifierNamingCheck::CT_CamelBack, "camelBack"}, + {readability::IdentifierNamingCheck::CT_CamelCase, "CamelCase"}, + {readability::IdentifierNamingCheck::CT_CamelSnakeCase, + "Camel_Snake_Case"}, + {readability::IdentifierNamingCheck::CT_CamelSnakeBack, + "camel_Snake_Back"}}; + return llvm::ArrayRef(Mapping); +} + +template <> +struct OptionEnumMapping< + readability::IdentifierNamingCheck::HungarianPrefixType> { + using HungarianPrefixType = + readability::IdentifierNamingCheck::HungarianPrefixType; + static llvm::ArrayRef<std::pair<HungarianPrefixType, StringRef>> + getEnumMapping() { + static constexpr std::pair<HungarianPrefixType, StringRef> Mapping[] = { + {HungarianPrefixType::HPT_Off, "Off"}, + {HungarianPrefixType::HPT_On, "On"}, + {HungarianPrefixType::HPT_LowerCase, "LowerCase"}, + {HungarianPrefixType::HPT_CamelCase, "CamelCase"}}; + return llvm::ArrayRef(Mapping); + } +}; + +namespace readability { + +// clang-format off +#define NAMING_KEYS(m) \ + m(Namespace) \ + m(InlineNamespace) \ + m(EnumConstant) \ + m(ScopedEnumConstant) \ + m(ConstexprVariable) \ + m(ConstantMember) \ + m(PrivateMember) \ + m(ProtectedMember) \ + m(PublicMember) \ + m(Member) \ + m(ClassConstant) \ + m(ClassMember) \ + m(GlobalConstant) \ + m(GlobalConstantPointer) \ + m(GlobalPointer) \ + m(GlobalVariable) \ + m(LocalConstant) \ + m(LocalConstantPointer) \ + m(LocalPointer) \ + m(LocalVariable) \ + m(StaticConstant) \ + m(StaticVariable) \ + m(Constant) \ + m(Variable) \ + m(ConstantParameter) \ + m(ParameterPack) \ + m(Parameter) \ + m(PointerParameter) \ + m(ConstantPointerParameter) \ + m(AbstractClass) \ + m(Struct) \ + m(Class) \ + m(Union) \ + m(Enum) \ + m(GlobalFunction) \ + m(ConstexprFunction) \ + m(Function) \ + m(ConstexprMethod) \ + m(VirtualMethod) \ + m(ClassMethod) \ + m(PrivateMethod) \ + m(ProtectedMethod) \ + m(PublicMethod) \ + m(Method) \ + m(Typedef) \ + m(TypeTemplateParameter) \ + m(ValueTemplateParameter) \ + m(TemplateTemplateParameter) \ + m(TemplateParameter) \ + m(TypeAlias) \ + m(MacroDefinition) \ + m(ObjcIvar) \ + +enum StyleKind : int { +#define ENUMERATE(v) SK_ ## v, + NAMING_KEYS(ENUMERATE) +#undef ENUMERATE + SK_Count, + SK_Invalid +}; + +static StringRef const StyleNames[] = { +#define STRINGIZE(v) #v, + NAMING_KEYS(STRINGIZE) +#undef STRINGIZE +}; + +#define HUNGARIAN_NOTATION_PRIMITIVE_TYPES(m) \ + m(int8_t) \ + m(int16_t) \ + m(int32_t) \ + m(int64_t) \ + m(uint8_t) \ + m(uint16_t) \ + m(uint32_t) \ + m(uint64_t) \ + m(char8_t) \ + m(char16_t) \ + m(char32_t) \ + m(float) \ + m(double) \ + m(char) \ + m(bool) \ + m(_Bool) \ + m(int) \ + m(size_t) \ + m(wchar_t) \ + m(short-int) \ + m(short) \ + m(signed-int) \ + m(signed-short) \ + m(signed-short-int) \ + m(signed-long-long-int) \ + m(signed-long-long) \ + m(signed-long-int) \ + m(signed-long) \ + m(signed) \ + m(unsigned-long-long-int) \ + m(unsigned-long-long) \ + m(unsigned-long-int) \ + m(unsigned-long) \ + m(unsigned-short-int) \ + m(unsigned-short) \ + m(unsigned-int) \ + m(unsigned) \ + m(long-long-int) \ + m(long-double) \ + m(long-long) \ + m(long-int) \ + m(long) \ + m(ptrdiff_t) \ + +static StringRef const HungarainNotationPrimitiveTypes[] = { +#define STRINGIZE(v) #v, + HUNGARIAN_NOTATION_PRIMITIVE_TYPES(STRINGIZE) +#undef STRINGIZE +}; + +#define HUNGARIAN_NOTATION_USER_DEFINED_TYPES(m) \ + m(BOOL) \ + m(BOOLEAN) \ + m(BYTE) \ + m(CHAR) \ + m(UCHAR) \ + m(SHORT) \ + m(USHORT) \ + m(WORD) \ + m(DWORD) \ + m(DWORD32) \ + m(DWORD64) \ + m(LONG) \ + m(ULONG) \ + m(ULONG32) \ + m(ULONG64) \ + m(ULONGLONG) \ + m(HANDLE) \ + m(INT) \ + m(INT8) \ + m(INT16) \ + m(INT32) \ + m(INT64) \ + m(UINT) \ + m(UINT8) \ + m(UINT16) \ + m(UINT32) \ + m(UINT64) \ + m(PVOID) \ + +static StringRef const HungarainNotationUserDefinedTypes[] = { +#define STRINGIZE(v) #v, + HUNGARIAN_NOTATION_USER_DEFINED_TYPES(STRINGIZE) +#undef STRINGIZE +}; + + +#undef NAMING_KEYS +// clang-format on + +IdentifierNamingCheck::NamingStyle::NamingStyle( + std::optional<IdentifierNamingCheck::CaseType> Case, + const std::string &Prefix, const std::string &Suffix, + const std::string &IgnoredRegexpStr, HungarianPrefixType HPType) + : Case(Case), Prefix(Prefix), Suffix(Suffix), + IgnoredRegexpStr(IgnoredRegexpStr), HPType(HPType) { + if (!IgnoredRegexpStr.empty()) { + IgnoredRegexp = + llvm::Regex(llvm::SmallString<128>({"^", IgnoredRegexpStr, "$"})); + if (!IgnoredRegexp.isValid()) + llvm::errs() << "Invalid IgnoredRegexp regular expression: " + << IgnoredRegexpStr; + } +} + +IdentifierNamingCheck::FileStyle IdentifierNamingCheck::getFileStyleFromOptions( + const ClangTidyCheck::OptionsView &Options) const { + IdentifierNamingCheck::HungarianNotationOption HNOption; + + HungarianNotation.loadDefaultConfig(HNOption); + HungarianNotation.loadFileConfig(Options, HNOption); + + SmallVector<std::optional<IdentifierNamingCheck::NamingStyle>, 0> Styles; + Styles.resize(SK_Count); + SmallString<64> StyleString; + for (unsigned I = 0; I < SK_Count; ++I) { + size_t StyleSize = StyleNames[I].size(); + StyleString.assign({StyleNames[I], "HungarianPrefix"}); + + auto HPTOpt = + Options.get<IdentifierNamingCheck::HungarianPrefixType>(StyleString); + if (HPTOpt && !HungarianNotation.checkOptionValid(I)) + configurationDiag("invalid identifier naming option '%0'") << StyleString; + + memcpy(&StyleString[StyleSize], "IgnoredRegexp", 13); + StyleString.truncate(StyleSize + 13); + StringRef IgnoredRegexpStr = Options.get(StyleString, ""); + memcpy(&StyleString[StyleSize], "Prefix", 6); + StyleString.truncate(StyleSize + 6); + std::string Prefix(Options.get(StyleString, "")); + // Fast replacement of [Pre]fix -> [Suf]fix. + memcpy(&StyleString[StyleSize], "Suf", 3); + std::string Postfix(Options.get(StyleString, "")); + memcpy(&StyleString[StyleSize], "Case", 4); + StyleString.pop_back_n(2); + auto CaseOptional = + Options.get<IdentifierNamingCheck::CaseType>(StyleString); + + if (CaseOptional || !Prefix.empty() || !Postfix.empty() || + !IgnoredRegexpStr.empty() || HPTOpt) + Styles[I].emplace(std::move(CaseOptional), std::move(Prefix), + std::move(Postfix), IgnoredRegexpStr.str(), + HPTOpt.value_or(IdentifierNamingCheck::HPT_Off)); + } + bool IgnoreMainLike = Options.get("IgnoreMainLikeFunctions", false); + return {std::move(Styles), std::move(HNOption), IgnoreMainLike}; +} + +std::string IdentifierNamingCheck::HungarianNotation::getDeclTypeName( + const NamedDecl *ND) const { + const auto *VD = dyn_cast<ValueDecl>(ND); + if (!VD) + return {}; + + if (isa<FunctionDecl, EnumConstantDecl>(ND)) + return {}; + + // Get type text of variable declarations. + auto &SM = VD->getASTContext().getSourceManager(); + const char *Begin = SM.getCharacterData(VD->getBeginLoc()); + const char *End = SM.getCharacterData(VD->getEndLoc()); + intptr_t StrLen = End - Begin; + + // FIXME: Sometimes the value that returns from ValDecl->getEndLoc() + // is wrong(out of location of Decl). This causes `StrLen` will be assigned + // an unexpected large value. Current workaround to find the terminated + // character instead of the `getEndLoc()` function. + const char *EOL = strchr(Begin, '\n'); + if (!EOL) + EOL = Begin + strlen(Begin); + + const char *PosList[] = {strchr(Begin, '='), strchr(Begin, ';'), + strchr(Begin, ','), strchr(Begin, ')'), EOL}; + for (const auto &Pos : PosList) { + if (Pos > Begin) + EOL = std::min(EOL, Pos); + } + + StrLen = EOL - Begin; + std::string TypeName; + if (StrLen > 0) { + std::string Type(Begin, StrLen); + + static constexpr StringRef Keywords[] = { + // Constexpr specifiers + "constexpr", "constinit", "consteval", + // Qualifier + "const", "volatile", "restrict", "mutable", + // Storage class specifiers + "register", "static", "extern", "thread_local", + // Other keywords + "virtual"}; + + // Remove keywords + for (StringRef Kw : Keywords) { + for (size_t Pos = 0; + (Pos = Type.find(Kw.data(), Pos)) != std::string::npos;) { + Type.replace(Pos, Kw.size(), ""); + } + } + TypeName = Type.erase(0, Type.find_first_not_of(" ")); + + // Replace spaces with single space. + for (size_t Pos = 0; (Pos = Type.find(" ", Pos)) != std::string::npos; + Pos += strlen(" ")) { + Type.replace(Pos, strlen(" "), " "); + } + + // Replace " &" with "&". + for (size_t Pos = 0; (Pos = Type.find(" &", Pos)) != std::string::npos; + Pos += strlen("&")) { + Type.replace(Pos, strlen(" &"), "&"); + } + + // Replace " *" with "* ". + for (size_t Pos = 0; (Pos = Type.find(" *", Pos)) != std::string::npos; + Pos += strlen("*")) { + Type.replace(Pos, strlen(" *"), "* "); + } + + // Remove redundant tailing. + static constexpr StringRef TailsOfMultiWordType[] = { + " int", " char", " double", " long", " short"}; + bool RedundantRemoved = false; + for (auto Kw : TailsOfMultiWordType) { + size_t Pos = Type.rfind(Kw.data()); + if (Pos != std::string::npos) { + Type = Type.substr(0, Pos + Kw.size()); + RedundantRemoved = true; + break; + } + } + TypeName = Type.erase(0, Type.find_first_not_of(" ")); + if (!RedundantRemoved) { + std::size_t FoundSpace = Type.find(" "); + if (FoundSpace != std::string::npos) + Type = Type.substr(0, FoundSpace); + } + + TypeName = Type.erase(0, Type.find_first_not_of(" ")); + + QualType QT = VD->getType(); + if (!QT.isNull() && QT->isArrayType()) + TypeName.append("[]"); + } + + return TypeName; +} + +IdentifierNamingCheck::IdentifierNamingCheck(StringRef Name, + ClangTidyContext *Context) + : RenamerClangTidyCheck(Name, Context), Context(Context), CheckName(Name), + GetConfigPerFile(Options.get("GetConfigPerFile", true)), + IgnoreFailedSplit(Options.get("IgnoreFailedSplit", false)) { + + auto IterAndInserted = NamingStylesCache.try_emplace( + llvm::sys::path::parent_path(Context->getCurrentFile()), + getFileStyleFromOptions(Options)); + assert(IterAndInserted.second && "Couldn't insert Style"); + // Holding a reference to the data in the vector is safe as it should never + // move. + MainFileStyle = &IterAndInserted.first->getValue(); +} + +IdentifierNamingCheck::~IdentifierNamingCheck() = default; + +bool IdentifierNamingCheck::HungarianNotation::checkOptionValid( + int StyleKindIndex) const { + if ((StyleKindIndex >= SK_EnumConstant) && + (StyleKindIndex <= SK_ConstantParameter)) + return true; + + if ((StyleKindIndex >= SK_Parameter) && (StyleKindIndex <= SK_Enum)) + return true; + + return false; +} + +bool IdentifierNamingCheck::HungarianNotation::isOptionEnabled( + StringRef OptionKey, const llvm::StringMap<std::string> &StrMap) const { + if (OptionKey.empty()) + return false; + + auto Iter = StrMap.find(OptionKey); + if (Iter == StrMap.end()) + return false; + + return *llvm::yaml::parseBool(Iter->getValue()); +} + +void IdentifierNamingCheck::HungarianNotation::loadFileConfig( + const ClangTidyCheck::OptionsView &Options, + IdentifierNamingCheck::HungarianNotationOption &HNOption) const { + + static constexpr StringRef HNOpts[] = {"TreatStructAsClass"}; + static constexpr StringRef HNDerivedTypes[] = {"Array", "Pointer", + "FunctionPointer"}; + + StringRef Section = "HungarianNotation."; + + SmallString<128> Buffer = {Section, "General."}; + size_t DefSize = Buffer.size(); + for (const auto &Opt : HNOpts) { + Buffer.truncate(DefSize); + Buffer.append(Opt); + StringRef Val = Options.get(Buffer, ""); + if (!Val.empty()) + HNOption.General[Opt] = Val.str(); + } + + Buffer = {Section, "DerivedType."}; + DefSize = Buffer.size(); + for (const auto &Type : HNDerivedTypes) { + Buffer.truncate(DefSize); + Buffer.append(Type); + StringRef Val = Options.get(Buffer, ""); + if (!Val.empty()) + HNOption.DerivedType[Type] = Val.str(); + } + + static constexpr std::pair<StringRef, StringRef> HNCStrings[] = { + {"CharPrinter", "char*"}, + {"CharArray", "char[]"}, + {"WideCharPrinter", "wchar_t*"}, + {"WideCharArray", "wchar_t[]"}}; + + Buffer = {Section, "CString."}; + DefSize = Buffer.size(); + for (const auto &CStr : HNCStrings) { + Buffer.truncate(DefSize); + Buffer.append(CStr.first); + StringRef Val = Options.get(Buffer, ""); + if (!Val.empty()) + HNOption.CString[CStr.first] = Val.str(); + } + + Buffer = {Section, "PrimitiveType."}; + DefSize = Buffer.size(); + for (const auto &PrimType : HungarainNotationPrimitiveTypes) { + Buffer.truncate(DefSize); + Buffer.append(PrimType); + StringRef Val = Options.get(Buffer, ""); + if (!Val.empty()) { + std::string Type = PrimType.str(); + std::replace(Type.begin(), Type.end(), '-', ' '); + HNOption.PrimitiveType[Type] = Val.str(); + } + } + + Buffer = {Section, "UserDefinedType."}; + DefSize = Buffer.size(); + for (const auto &Type : HungarainNotationUserDefinedTypes) { + Buffer.truncate(DefSize); + Buffer.append(Type); + StringRef Val = Options.get(Buffer, ""); + if (!Val.empty()) + HNOption.UserDefinedType[Type] = Val.str(); + } +} + +std::string IdentifierNamingCheck::HungarianNotation::getPrefix( + const Decl *D, + const IdentifierNamingCheck::HungarianNotationOption &HNOption) const { + if (!D) + return {}; + const auto *ND = dyn_cast<NamedDecl>(D); + if (!ND) + return {}; + + std::string Prefix; + if (const auto *ECD = dyn_cast<EnumConstantDecl>(ND)) { + Prefix = getEnumPrefix(ECD); + } else if (const auto *CRD = dyn_cast<CXXRecordDecl>(ND)) { + Prefix = getClassPrefix(CRD, HNOption); + } else if (isa<VarDecl, FieldDecl, RecordDecl>(ND)) { + std::string TypeName = getDeclTypeName(ND); + if (!TypeName.empty()) + Prefix = getDataTypePrefix(TypeName, ND, HNOption); + } + + return Prefix; +} + +bool IdentifierNamingCheck::HungarianNotation::removeDuplicatedPrefix( + SmallVector<StringRef, 8> &Words, + const IdentifierNamingCheck::HungarianNotationOption &HNOption) const { + if (Words.size() <= 1) + return true; + + std::string CorrectName = Words[0].str(); + std::vector<llvm::StringMap<std::string>> MapList = { + HNOption.CString, HNOption.DerivedType, HNOption.PrimitiveType, + HNOption.UserDefinedType}; + + for (const auto &Map : MapList) { + for (const auto &Str : Map) { + if (Str.getValue() == CorrectName) { + Words.erase(Words.begin(), Words.begin() + 1); + return true; + } + } + } + + return false; +} + +std::string IdentifierNamingCheck::HungarianNotation::getDataTypePrefix( + StringRef TypeName, const NamedDecl *ND, + const IdentifierNamingCheck::HungarianNotationOption &HNOption) const { + if (!ND || TypeName.empty()) + return TypeName.str(); + + std::string ModifiedTypeName(TypeName); + + // Derived types + std::string PrefixStr; + if (const auto *TD = dyn_cast<ValueDecl>(ND)) { + QualType QT = TD->getType(); + if (QT->isFunctionPointerType()) { + PrefixStr = HNOption.DerivedType.lookup("FunctionPointer"); + } else if (QT->isPointerType()) { + for (const auto &CStr : HNOption.CString) { + std::string Key = CStr.getKey().str(); + if (ModifiedTypeName.find(Key) == 0) { + PrefixStr = CStr.getValue(); + ModifiedTypeName = ModifiedTypeName.substr( + Key.size(), ModifiedTypeName.size() - Key.size()); + break; + } + } + } else if (QT->isArrayType()) { + for (const auto &CStr : HNOption.CString) { + std::string Key = CStr.getKey().str(); + if (ModifiedTypeName.find(Key) == 0) { + PrefixStr = CStr.getValue(); + break; + } + } + if (PrefixStr.empty()) + PrefixStr = HNOption.DerivedType.lookup("Array"); + } else if (QT->isReferenceType()) { + size_t Pos = ModifiedTypeName.find_last_of("&"); + if (Pos != std::string::npos) + ModifiedTypeName = ModifiedTypeName.substr(0, Pos); + } + } + + // Pointers + size_t PtrCount = [&](std::string TypeName) -> size_t { + size_t Pos = TypeName.find('*'); + size_t Count = 0; + for (; Pos < TypeName.length(); Pos++, Count++) { + if ('*' != TypeName[Pos]) + break; + } + return Count; + }(ModifiedTypeName); + if (PtrCount > 0) { + ModifiedTypeName = [&](std::string Str, StringRef From, StringRef To) { + size_t StartPos = 0; + while ((StartPos = Str.find(From.data(), StartPos)) != + std::string::npos) { + Str.replace(StartPos, From.size(), To.data()); + StartPos += To.size(); + } + return Str; + }(ModifiedTypeName, "*", ""); + } + + // Primitive types + if (PrefixStr.empty()) { + for (const auto &Type : HNOption.PrimitiveType) { + if (ModifiedTypeName == Type.getKey()) { + PrefixStr = Type.getValue(); + break; + } + } + } + + // User-Defined types + if (PrefixStr.empty()) { + for (const auto &Type : HNOption.UserDefinedType) { + if (ModifiedTypeName == Type.getKey()) { + PrefixStr = Type.getValue(); + break; + } + } + } + + for (size_t Idx = 0; Idx < PtrCount; Idx++) + PrefixStr.insert(0, HNOption.DerivedType.lookup("Pointer")); + + return PrefixStr; +} + +std::string IdentifierNamingCheck::HungarianNotation::getClassPrefix( + const CXXRecordDecl *CRD, + const IdentifierNamingCheck::HungarianNotationOption &HNOption) const { + + if (CRD->isUnion()) + return {}; + + if (CRD->isStruct() && + !isOptionEnabled("TreatStructAsClass", HNOption.General)) + return {}; + + return CRD->isAbstract() ? "I" : "C"; +} + +std::string IdentifierNamingCheck::HungarianNotation::getEnumPrefix( + const EnumConstantDecl *ECD) const { + std::string Name = ECD->getType().getAsString(); + if (std::string::npos != Name.find("enum")) { + Name = Name.substr(strlen("enum"), Name.length() - strlen("enum")); + Name = Name.erase(0, Name.find_first_not_of(" ")); + } + + static llvm::Regex Splitter( + "([a-z0-9A-Z]*)(_+)|([A-Z]?[a-z0-9]+)([A-Z]|$)|([A-Z]+)([A-Z]|$)"); + + StringRef EnumName(Name); + SmallVector<StringRef, 8> Substrs; + EnumName.split(Substrs, "_", -1, false); + + SmallVector<StringRef, 8> Words; + SmallVector<StringRef, 8> Groups; + for (auto Substr : Substrs) { + while (!Substr.empty()) { + Groups.clear(); + if (!Splitter.match(Substr, &Groups)) + break; + + if (Groups[2].size() > 0) { + Words.push_back(Groups[1]); + Substr = Substr.substr(Groups[0].size()); + } else if (Groups[3].size() > 0) { + Words.push_back(Groups[3]); + Substr = Substr.substr(Groups[0].size() - Groups[4].size()); + } else if (Groups[5].size() > 0) { + Words.push_back(Groups[5]); + Substr = Substr.substr(Groups[0].size() - Groups[6].size()); + } + } + } + + std::string Initial; + for (StringRef Word : Words) + Initial += tolower(Word[0]); + + return Initial; +} + +void IdentifierNamingCheck::HungarianNotation::loadDefaultConfig( + IdentifierNamingCheck::HungarianNotationOption &HNOption) const { + + // Options + static constexpr std::pair<StringRef, StringRef> General[] = { + {"TreatStructAsClass", "false"}}; + for (const auto &G : General) + HNOption.General.try_emplace(G.first, G.second); + + // Derived types + static constexpr std::pair<StringRef, StringRef> DerivedTypes[] = { + {"Array", "a"}, {"Pointer", "p"}, {"FunctionPointer", "fn"}}; + for (const auto &DT : DerivedTypes) + HNOption.DerivedType.try_emplace(DT.first, DT.second); + + // C strings + static constexpr std::pair<StringRef, StringRef> CStrings[] = { + {"char*", "sz"}, + {"char[]", "sz"}, + {"wchar_t*", "wsz"}, + {"wchar_t[]", "wsz"}}; + for (const auto &CStr : CStrings) + HNOption.CString.try_emplace(CStr.first, CStr.second); + + // clang-format off + static constexpr std::pair<StringRef, StringRef> PrimitiveTypes[] = { + {"int8_t", "i8" }, + {"int16_t", "i16" }, + {"int32_t", "i32" }, + {"int64_t", "i64" }, + {"uint8_t", "u8" }, + {"uint16_t", "u16" }, + {"uint32_t", "u32" }, + {"uint64_t", "u64" }, + {"char8_t", "c8" }, + {"char16_t", "c16" }, + {"char32_t", "c32" }, + {"float", "f" }, + {"double", "d" }, + {"char", "c" }, + {"bool", "b" }, + {"_Bool", "b" }, + {"int", "i" }, + {"size_t", "n" }, + {"wchar_t", "wc" }, + {"short int", "si" }, + {"short", "s" }, + {"signed int", "si" }, + {"signed short", "ss" }, + {"signed short int", "ssi" }, + {"signed long long int", "slli"}, + {"signed long long", "sll" }, + {"signed long int", "sli" }, + {"signed long", "sl" }, + {"signed", "s" }, + {"unsigned long long int", "ulli"}, + {"unsigned long long", "ull" }, + {"unsigned long int", "uli" }, + {"unsigned long", "ul" }, + {"unsigned short int", "usi" }, + {"unsigned short", "us" }, + {"unsigned int", "ui" }, + {"unsigned", "u" }, + {"long long int", "lli" }, + {"long double", "ld" }, + {"long long", "ll" }, + {"long int", "li" }, + {"long", "l" }, + {"ptrdiff_t", "p" }}; + // clang-format on + for (const auto &PT : PrimitiveTypes) + HNOption.PrimitiveType.try_emplace(PT.first, PT.second); + + // clang-format off + static constexpr std::pair<StringRef, StringRef> UserDefinedTypes[] = { + // Windows data types + {"BOOL", "b" }, + {"BOOLEAN", "b" }, + {"BYTE", "by" }, + {"CHAR", "c" }, + {"UCHAR", "uc" }, + {"SHORT", "s" }, + {"USHORT", "us" }, + {"WORD", "w" }, + {"DWORD", "dw" }, + {"DWORD32", "dw32"}, + {"DWORD64", "dw64"}, + {"LONG", "l" }, + {"ULONG", "ul" }, + {"ULONG32", "ul32"}, + {"ULONG64", "ul64"}, + {"ULONGLONG", "ull" }, + {"HANDLE", "h" }, + {"INT", "i" }, + {"INT8", "i8" }, + {"INT16", "i16" }, + {"INT32", "i32" }, + {"INT64", "i64" }, + {"UINT", "ui" }, + {"UINT8", "u8" }, + {"UINT16", "u16" }, + {"UINT32", "u32" }, + {"UINT64", "u64" }, + {"PVOID", "p" } }; + // clang-format on + for (const auto &UDT : UserDefinedTypes) + HNOption.UserDefinedType.try_emplace(UDT.first, UDT.second); +} + +void IdentifierNamingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + RenamerClangTidyCheck::storeOptions(Opts); + SmallString<64> StyleString; + ArrayRef<std::optional<NamingStyle>> Styles = MainFileStyle->getStyles(); + for (size_t I = 0; I < SK_Count; ++I) { + if (!Styles[I]) + continue; + size_t StyleSize = StyleNames[I].size(); + StyleString.assign({StyleNames[I], "HungarianPrefix"}); + + Options.store(Opts, StyleString, Styles[I]->HPType); + + memcpy(&StyleString[StyleSize], "IgnoredRegexp", 13); + StyleString.truncate(StyleSize + 13); + Options.store(Opts, StyleString, Styles[I]->IgnoredRegexpStr); + memcpy(&StyleString[StyleSize], "Prefix", 6); + StyleString.truncate(StyleSize + 6); + Options.store(Opts, StyleString, Styles[I]->Prefix); + // Fast replacement of [Pre]fix -> [Suf]fix. + memcpy(&StyleString[StyleSize], "Suf", 3); + Options.store(Opts, StyleString, Styles[I]->Suffix); + if (Styles[I]->Case) { + memcpy(&StyleString[StyleSize], "Case", 4); + StyleString.pop_back_n(2); + Options.store(Opts, StyleString, *Styles[I]->Case); + } + } + Options.store(Opts, "GetConfigPerFile", GetConfigPerFile); + Options.store(Opts, "IgnoreFailedSplit", IgnoreFailedSplit); + Options.store(Opts, "IgnoreMainLikeFunctions", + MainFileStyle->isIgnoringMainLikeFunction()); +} + +bool IdentifierNamingCheck::matchesStyle( + StringRef Type, StringRef Name, + const IdentifierNamingCheck::NamingStyle &Style, + const IdentifierNamingCheck::HungarianNotationOption &HNOption, + const NamedDecl *Decl) const { + static llvm::Regex Matchers[] = { + llvm::Regex("^.*$"), + llvm::Regex("^[a-z][a-z0-9_]*$"), + llvm::Regex("^[a-z][a-zA-Z0-9]*$"), + llvm::Regex("^[A-Z][A-Z0-9_]*$"), + llvm::Regex("^[A-Z][a-zA-Z0-9]*$"), + llvm::Regex("^[A-Z]([a-z0-9]*(_[A-Z])?)*"), + llvm::Regex("^[a-z]([a-z0-9]*(_[A-Z])?)*"), + }; + + if (!Name.consume_front(Style.Prefix)) + return false; + if (!Name.consume_back(Style.Suffix)) + return false; + if (IdentifierNamingCheck::HungarianPrefixType::HPT_Off != Style.HPType) { + std::string HNPrefix = HungarianNotation.getPrefix(Decl, HNOption); + if (!Name.consume_front(HNPrefix)) + return false; + } + + // Ensure the name doesn't have any extra underscores beyond those specified + // in the prefix and suffix. + if (Name.startswith("_") || Name.endswith("_")) + return false; + + if (Style.Case && !Matchers[static_cast<size_t>(*Style.Case)].match(Name)) + return false; + + return true; +} + +std::string IdentifierNamingCheck::fixupWithCase( + StringRef Type, StringRef Name, const Decl *D, + const IdentifierNamingCheck::NamingStyle &Style, + const IdentifierNamingCheck::HungarianNotationOption &HNOption, + IdentifierNamingCheck::CaseType Case) const { + static llvm::Regex Splitter( + "([a-z0-9A-Z]*)(_+)|([A-Z]?[a-z0-9]+)([A-Z]|$)|([A-Z]+)([A-Z]|$)"); + + SmallVector<StringRef, 8> Substrs; + Name.split(Substrs, "_", -1, false); + + SmallVector<StringRef, 8> Words; + SmallVector<StringRef, 8> Groups; + for (auto Substr : Substrs) { + while (!Substr.empty()) { + Groups.clear(); + if (!Splitter.match(Substr, &Groups)) + break; + + if (Groups[2].size() > 0) { + Words.push_back(Groups[1]); + Substr = Substr.substr(Groups[0].size()); + } else if (Groups[3].size() > 0) { + Words.push_back(Groups[3]); + Substr = Substr.substr(Groups[0].size() - Groups[4].size()); + } else if (Groups[5].size() > 0) { + Words.push_back(Groups[5]); + Substr = Substr.substr(Groups[0].size() - Groups[6].size()); + } + } + } + + if (Words.empty()) + return Name.str(); + + if (IdentifierNamingCheck::HungarianPrefixType::HPT_Off != Style.HPType) { + HungarianNotation.removeDuplicatedPrefix(Words, HNOption); + } + + SmallString<128> Fixup; + switch (Case) { + case IdentifierNamingCheck::CT_AnyCase: + return Name.str(); + break; + + case IdentifierNamingCheck::CT_LowerCase: + for (auto const &Word : Words) { + if (&Word != &Words.front()) + Fixup += "_"; + Fixup += Word.lower(); + } + break; + + case IdentifierNamingCheck::CT_UpperCase: + for (auto const &Word : Words) { + if (&Word != &Words.front()) + Fixup += "_"; + Fixup += Word.upper(); + } + break; + + case IdentifierNamingCheck::CT_CamelCase: + for (auto const &Word : Words) { + Fixup += toupper(Word.front()); + Fixup += Word.substr(1).lower(); + } + break; + + case IdentifierNamingCheck::CT_CamelBack: + for (auto const &Word : Words) { + if (&Word == &Words.front()) { + Fixup += Word.lower(); + } else { + Fixup += toupper(Word.front()); + Fixup += Word.substr(1).lower(); + } + } + break; + + case IdentifierNamingCheck::CT_CamelSnakeCase: + for (auto const &Word : Words) { + if (&Word != &Words.front()) + Fixup += "_"; + Fixup += toupper(Word.front()); + Fixup += Word.substr(1).lower(); + } + break; + + case IdentifierNamingCheck::CT_CamelSnakeBack: + for (auto const &Word : Words) { + if (&Word != &Words.front()) { + Fixup += "_"; + Fixup += toupper(Word.front()); + } else { + Fixup += tolower(Word.front()); + } + Fixup += Word.substr(1).lower(); + } + break; + } + + return Fixup.str().str(); +} + +bool IdentifierNamingCheck::isParamInMainLikeFunction( + const ParmVarDecl &ParmDecl, bool IncludeMainLike) const { + const auto *FDecl = + dyn_cast_or_null<FunctionDecl>(ParmDecl.getParentFunctionOrMethod()); + if (!FDecl) + return false; + if (FDecl->isMain()) + return true; + if (!IncludeMainLike) + return false; + if (FDecl->getAccess() != AS_public && FDecl->getAccess() != AS_none) + return false; + // If the function doesn't have a name that's an identifier, can occur if the + // function is an operator overload, bail out early. + if (!FDecl->getDeclName().isIdentifier()) + return false; + enum MainType { None, Main, WMain }; + auto IsCharPtrPtr = [](QualType QType) -> MainType { + if (QType.isNull()) + return None; + if (QType = QType->getPointeeType(), QType.isNull()) + return None; + if (QType = QType->getPointeeType(), QType.isNull()) + return None; + if (QType->isCharType()) + return Main; + if (QType->isWideCharType()) + return WMain; + return None; + }; + auto IsIntType = [](QualType QType) { + if (QType.isNull()) + return false; + if (const auto *Builtin = + dyn_cast<BuiltinType>(QType->getUnqualifiedDesugaredType())) { + return Builtin->getKind() == BuiltinType::Int; + } + return false; + }; + if (!IsIntType(FDecl->getReturnType())) + return false; + if (FDecl->getNumParams() < 2 || FDecl->getNumParams() > 3) + return false; + if (!IsIntType(FDecl->parameters()[0]->getType())) + return false; + MainType Type = IsCharPtrPtr(FDecl->parameters()[1]->getType()); + if (Type == None) + return false; + if (FDecl->getNumParams() == 3 && + IsCharPtrPtr(FDecl->parameters()[2]->getType()) != Type) + return false; + + if (Type == Main) { + static llvm::Regex Matcher( + "(^[Mm]ain([_A-Z]|$))|([a-z0-9_]Main([_A-Z]|$))|(_main(_|$))"); + assert(Matcher.isValid() && "Invalid Matcher for main like functions."); + return Matcher.match(FDecl->getName()); + } + static llvm::Regex Matcher("(^((W[Mm])|(wm))ain([_A-Z]|$))|([a-z0-9_]W[Mm]" + "ain([_A-Z]|$))|(_wmain(_|$))"); + assert(Matcher.isValid() && "Invalid Matcher for wmain like functions."); + return Matcher.match(FDecl->getName()); +} + +std::string IdentifierNamingCheck::fixupWithStyle( + StringRef Type, StringRef Name, + const IdentifierNamingCheck::NamingStyle &Style, + const IdentifierNamingCheck::HungarianNotationOption &HNOption, + const Decl *D) const { + Name.consume_front(Style.Prefix); + Name.consume_back(Style.Suffix); + std::string Fixed = fixupWithCase( + Type, Name, D, Style, HNOption, + Style.Case.value_or(IdentifierNamingCheck::CaseType::CT_AnyCase)); + + std::string HungarianPrefix; + using HungarianPrefixType = IdentifierNamingCheck::HungarianPrefixType; + if (HungarianPrefixType::HPT_Off != Style.HPType) { + HungarianPrefix = HungarianNotation.getPrefix(D, HNOption); + if (!HungarianPrefix.empty()) { + if (Style.HPType == HungarianPrefixType::HPT_LowerCase) + HungarianPrefix += "_"; + + if (Style.HPType == HungarianPrefixType::HPT_CamelCase) + Fixed[0] = toupper(Fixed[0]); + } + } + StringRef Mid = StringRef(Fixed).trim("_"); + if (Mid.empty()) + Mid = "_"; + + return (Style.Prefix + HungarianPrefix + Mid + Style.Suffix).str(); +} + +StyleKind IdentifierNamingCheck::findStyleKind( + const NamedDecl *D, + ArrayRef<std::optional<IdentifierNamingCheck::NamingStyle>> NamingStyles, + bool IgnoreMainLikeFunctions) const { + assert(D && D->getIdentifier() && !D->getName().empty() && !D->isImplicit() && + "Decl must be an explicit identifier with a name."); + + if (isa<ObjCIvarDecl>(D) && NamingStyles[SK_ObjcIvar]) + return SK_ObjcIvar; + + if (isa<TypedefDecl>(D) && NamingStyles[SK_Typedef]) + return SK_Typedef; + + if (isa<TypeAliasDecl>(D) && NamingStyles[SK_TypeAlias]) + return SK_TypeAlias; + + if (const auto *Decl = dyn_cast<NamespaceDecl>(D)) { + if (Decl->isAnonymousNamespace()) + return SK_Invalid; + + if (Decl->isInline() && NamingStyles[SK_InlineNamespace]) + return SK_InlineNamespace; + + if (NamingStyles[SK_Namespace]) + return SK_Namespace; + } + + if (isa<EnumDecl>(D) && NamingStyles[SK_Enum]) + return SK_Enum; + + if (const auto *EnumConst = dyn_cast<EnumConstantDecl>(D)) { + if (cast<EnumDecl>(EnumConst->getDeclContext())->isScoped() && + NamingStyles[SK_ScopedEnumConstant]) + return SK_ScopedEnumConstant; + + if (NamingStyles[SK_EnumConstant]) + return SK_EnumConstant; + + if (NamingStyles[SK_Constant]) + return SK_Constant; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast<CXXRecordDecl>(D)) { + if (Decl->isAnonymousStructOrUnion()) + return SK_Invalid; + + if (!Decl->getCanonicalDecl()->isThisDeclarationADefinition()) + return SK_Invalid; + + if (Decl->hasDefinition() && Decl->isAbstract() && + NamingStyles[SK_AbstractClass]) + return SK_AbstractClass; + + if (Decl->isStruct() && NamingStyles[SK_Struct]) + return SK_Struct; + + if (Decl->isStruct() && NamingStyles[SK_Class]) + return SK_Class; + + if (Decl->isClass() && NamingStyles[SK_Class]) + return SK_Class; + + if (Decl->isClass() && NamingStyles[SK_Struct]) + return SK_Struct; + + if (Decl->isUnion() && NamingStyles[SK_Union]) + return SK_Union; + + if (Decl->isEnum() && NamingStyles[SK_Enum]) + return SK_Enum; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast<FieldDecl>(D)) { + QualType Type = Decl->getType(); + + if (!Type.isNull() && Type.isConstQualified()) { + if (NamingStyles[SK_ConstantMember]) + return SK_ConstantMember; + + if (NamingStyles[SK_Constant]) + return SK_Constant; + } + + if (Decl->getAccess() == AS_private && NamingStyles[SK_PrivateMember]) + return SK_PrivateMember; + + if (Decl->getAccess() == AS_protected && NamingStyles[SK_ProtectedMember]) + return SK_ProtectedMember; + + if (Decl->getAccess() == AS_public && NamingStyles[SK_PublicMember]) + return SK_PublicMember; + + if (NamingStyles[SK_Member]) + return SK_Member; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast<ParmVarDecl>(D)) { + if (isParamInMainLikeFunction(*Decl, IgnoreMainLikeFunctions)) + return SK_Invalid; + QualType Type = Decl->getType(); + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprVariable]) + return SK_ConstexprVariable; + + if (!Type.isNull() && Type.isConstQualified()) { + if (Type.getTypePtr()->isAnyPointerType() && + NamingStyles[SK_ConstantPointerParameter]) + return SK_ConstantPointerParameter; + + if (NamingStyles[SK_ConstantParameter]) + return SK_ConstantParameter; + + if (NamingStyles[SK_Constant]) + return SK_Constant; + } + + if (Decl->isParameterPack() && NamingStyles[SK_ParameterPack]) + return SK_ParameterPack; + + if (!Type.isNull() && Type.getTypePtr()->isAnyPointerType() && + NamingStyles[SK_PointerParameter]) + return SK_PointerParameter; + + if (NamingStyles[SK_Parameter]) + return SK_Parameter; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast<VarDecl>(D)) { + QualType Type = Decl->getType(); + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprVariable]) + return SK_ConstexprVariable; + + if (!Type.isNull() && Type.isConstQualified()) { + if (Decl->isStaticDataMember() && NamingStyles[SK_ClassConstant]) + return SK_ClassConstant; + + if (Decl->isFileVarDecl() && Type.getTypePtr()->isAnyPointerType() && + NamingStyles[SK_GlobalConstantPointer]) + return SK_GlobalConstantPointer; + + if (Decl->isFileVarDecl() && NamingStyles[SK_GlobalConstant]) + return SK_GlobalConstant; + + if (Decl->isStaticLocal() && NamingStyles[SK_StaticConstant]) + return SK_StaticConstant; + + if (Decl->isLocalVarDecl() && Type.getTypePtr()->isAnyPointerType() && + NamingStyles[SK_LocalConstantPointer]) + return SK_LocalConstantPointer; + + if (Decl->isLocalVarDecl() && NamingStyles[SK_LocalConstant]) + return SK_LocalConstant; + + if (Decl->isFunctionOrMethodVarDecl() && NamingStyles[SK_LocalConstant]) + return SK_LocalConstant; + + if (NamingStyles[SK_Constant]) + return SK_Constant; + } + + if (Decl->isStaticDataMember() && NamingStyles[SK_ClassMember]) + return SK_ClassMember; + + if (Decl->isFileVarDecl() && Type.getTypePtr()->isAnyPointerType() && + NamingStyles[SK_GlobalPointer]) + return SK_GlobalPointer; + + if (Decl->isFileVarDecl() && NamingStyles[SK_GlobalVariable]) + return SK_GlobalVariable; + + if (Decl->isStaticLocal() && NamingStyles[SK_StaticVariable]) + return SK_StaticVariable; + + if (Decl->isLocalVarDecl() && Type.getTypePtr()->isAnyPointerType() && + NamingStyles[SK_LocalPointer]) + return SK_LocalPointer; + + if (Decl->isLocalVarDecl() && NamingStyles[SK_LocalVariable]) + return SK_LocalVariable; + + if (Decl->isFunctionOrMethodVarDecl() && NamingStyles[SK_LocalVariable]) + return SK_LocalVariable; + + if (NamingStyles[SK_Variable]) + return SK_Variable; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast<CXXMethodDecl>(D)) { + if (Decl->isMain() || !Decl->isUserProvided() || + Decl->size_overridden_methods() > 0 || Decl->hasAttr<OverrideAttr>()) + return SK_Invalid; + + // If this method has the same name as any base method, this is likely + // necessary even if it's not an override. e.g. CRTP. + for (const CXXBaseSpecifier &Base : Decl->getParent()->bases()) + if (const auto *RD = Base.getType()->getAsCXXRecordDecl()) + if (RD->hasMemberName(Decl->getDeclName())) + return SK_Invalid; + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprMethod]) + return SK_ConstexprMethod; + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprFunction]) + return SK_ConstexprFunction; + + if (Decl->isStatic() && NamingStyles[SK_ClassMethod]) + return SK_ClassMethod; + + if (Decl->isVirtual() && NamingStyles[SK_VirtualMethod]) + return SK_VirtualMethod; + + if (Decl->getAccess() == AS_private && NamingStyles[SK_PrivateMethod]) + return SK_PrivateMethod; + + if (Decl->getAccess() == AS_protected && NamingStyles[SK_ProtectedMethod]) + return SK_ProtectedMethod; + + if (Decl->getAccess() == AS_public && NamingStyles[SK_PublicMethod]) + return SK_PublicMethod; + + if (NamingStyles[SK_Method]) + return SK_Method; + + if (NamingStyles[SK_Function]) + return SK_Function; + + return SK_Invalid; + } + + if (const auto *Decl = dyn_cast<FunctionDecl>(D)) { + if (Decl->isMain()) + return SK_Invalid; + + if (Decl->isConstexpr() && NamingStyles[SK_ConstexprFunction]) + return SK_ConstexprFunction; + + if (Decl->isGlobal() && NamingStyles[SK_GlobalFunction]) + return SK_GlobalFunction; + + if (NamingStyles[SK_Function]) + return SK_Function; + } + + if (isa<TemplateTypeParmDecl>(D)) { + if (NamingStyles[SK_TypeTemplateParameter]) + return SK_TypeTemplateParameter; + + if (NamingStyles[SK_TemplateParameter]) + return SK_TemplateParameter; + + return SK_Invalid; + } + + if (isa<NonTypeTemplateParmDecl>(D)) { + if (NamingStyles[SK_ValueTemplateParameter]) + return SK_ValueTemplateParameter; + + if (NamingStyles[SK_TemplateParameter]) + return SK_TemplateParameter; + + return SK_Invalid; + } + + if (isa<TemplateTemplateParmDecl>(D)) { + if (NamingStyles[SK_TemplateTemplateParameter]) + return SK_TemplateTemplateParameter; + + if (NamingStyles[SK_TemplateParameter]) + return SK_TemplateParameter; + + return SK_Invalid; + } + + return SK_Invalid; +} + +std::optional<RenamerClangTidyCheck::FailureInfo> +IdentifierNamingCheck::getFailureInfo( + StringRef Type, StringRef Name, const NamedDecl *ND, + SourceLocation Location, + ArrayRef<std::optional<IdentifierNamingCheck::NamingStyle>> NamingStyles, + const IdentifierNamingCheck::HungarianNotationOption &HNOption, + StyleKind SK, const SourceManager &SM, bool IgnoreFailedSplit) const { + if (SK == SK_Invalid || !NamingStyles[SK]) + return std::nullopt; + + const IdentifierNamingCheck::NamingStyle &Style = *NamingStyles[SK]; + if (Style.IgnoredRegexp.isValid() && Style.IgnoredRegexp.match(Name)) + return std::nullopt; + + if (matchesStyle(Type, Name, Style, HNOption, ND)) + return std::nullopt; + + std::string KindName = + fixupWithCase(Type, StyleNames[SK], ND, Style, HNOption, + IdentifierNamingCheck::CT_LowerCase); + std::replace(KindName.begin(), KindName.end(), '_', ' '); + + std::string Fixup = fixupWithStyle(Type, Name, Style, HNOption, ND); + if (StringRef(Fixup).equals(Name)) { + if (!IgnoreFailedSplit) { + LLVM_DEBUG(Location.print(llvm::dbgs(), SM); + llvm::dbgs() + << llvm::formatv(": unable to split words for {0} '{1}'\n", + KindName, Name)); + } + return std::nullopt; + } + return RenamerClangTidyCheck::FailureInfo{std::move(KindName), + std::move(Fixup)}; +} + +std::optional<RenamerClangTidyCheck::FailureInfo> +IdentifierNamingCheck::getDeclFailureInfo(const NamedDecl *Decl, + const SourceManager &SM) const { + SourceLocation Loc = Decl->getLocation(); + const FileStyle &FileStyle = getStyleForFile(SM.getFilename(Loc)); + if (!FileStyle.isActive()) + return std::nullopt; + + return getFailureInfo(HungarianNotation.getDeclTypeName(Decl), + Decl->getName(), Decl, Loc, FileStyle.getStyles(), + FileStyle.getHNOption(), + findStyleKind(Decl, FileStyle.getStyles(), + FileStyle.isIgnoringMainLikeFunction()), + SM, IgnoreFailedSplit); +} + +std::optional<RenamerClangTidyCheck::FailureInfo> +IdentifierNamingCheck::getMacroFailureInfo(const Token &MacroNameTok, + const SourceManager &SM) const { + SourceLocation Loc = MacroNameTok.getLocation(); + const FileStyle &Style = getStyleForFile(SM.getFilename(Loc)); + if (!Style.isActive()) + return std::nullopt; + + return getFailureInfo("", MacroNameTok.getIdentifierInfo()->getName(), + nullptr, Loc, Style.getStyles(), Style.getHNOption(), + SK_MacroDefinition, SM, IgnoreFailedSplit); +} + +RenamerClangTidyCheck::DiagInfo +IdentifierNamingCheck::getDiagInfo(const NamingCheckId &ID, + const NamingCheckFailure &Failure) const { + return DiagInfo{"invalid case style for %0 '%1'", + [&](DiagnosticBuilder &Diag) { + Diag << Failure.Info.KindName << ID.second; + }}; +} + +const IdentifierNamingCheck::FileStyle & +IdentifierNamingCheck::getStyleForFile(StringRef FileName) const { + if (!GetConfigPerFile) + return *MainFileStyle; + StringRef Parent = llvm::sys::path::parent_path(FileName); + auto Iter = NamingStylesCache.find(Parent); + if (Iter != NamingStylesCache.end()) + return Iter->getValue(); + + ClangTidyOptions Options = Context->getOptionsForFile(FileName); + if (Options.Checks && GlobList(*Options.Checks).contains(CheckName)) { + auto It = NamingStylesCache.try_emplace( + Parent, + getFileStyleFromOptions({CheckName, Options.CheckOptions, Context})); + assert(It.second); + return It.first->getValue(); + } + // Default construction gives an empty style. + auto It = NamingStylesCache.try_emplace(Parent); + assert(It.second); + return It.first->getValue(); +} + +} // namespace readability +} // namespace clang::tidy diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/IdentifierNamingCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/IdentifierNamingCheck.h new file mode 100644 index 0000000000..cbf0653a28 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/IdentifierNamingCheck.h @@ -0,0 +1,215 @@ +//===--- IdentifierNamingCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H + +#include "../utils/RenamerClangTidyCheck.h" +#include <optional> +namespace clang::tidy { +namespace readability { + +enum StyleKind : int; + +/// Checks for identifiers naming style mismatch. +/// +/// This check will try to enforce coding guidelines on the identifiers naming. +/// It supports `lower_case`, `UPPER_CASE`, `camelBack` and `CamelCase` casing +/// and tries to convert from one to another if a mismatch is detected. +/// +/// It also supports a fixed prefix and suffix that will be prepended or +/// appended to the identifiers, regardless of the casing. +/// +/// Many configuration options are available, in order to be able to create +/// different rules for different kind of identifier. In general, the +/// rules are falling back to a more generic rule if the specific case is not +/// configured. +class IdentifierNamingCheck final : public RenamerClangTidyCheck { +public: + IdentifierNamingCheck(StringRef Name, ClangTidyContext *Context); + ~IdentifierNamingCheck(); + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + + enum CaseType { + CT_AnyCase = 0, + CT_LowerCase, + CT_CamelBack, + CT_UpperCase, + CT_CamelCase, + CT_CamelSnakeCase, + CT_CamelSnakeBack + }; + + enum HungarianPrefixType { + HPT_Off = 0, + HPT_On, + HPT_LowerCase, + HPT_CamelCase, + }; + + struct HungarianNotationOption { + HungarianNotationOption() : HPType(HungarianPrefixType::HPT_Off) {} + + std::optional<CaseType> Case; + HungarianPrefixType HPType; + llvm::StringMap<std::string> General; + llvm::StringMap<std::string> CString; + llvm::StringMap<std::string> PrimitiveType; + llvm::StringMap<std::string> UserDefinedType; + llvm::StringMap<std::string> DerivedType; + }; + + struct NamingStyle { + NamingStyle() = default; + + NamingStyle(std::optional<CaseType> Case, const std::string &Prefix, + const std::string &Suffix, const std::string &IgnoredRegexpStr, + HungarianPrefixType HPType); + NamingStyle(const NamingStyle &O) = delete; + NamingStyle &operator=(NamingStyle &&O) = default; + NamingStyle(NamingStyle &&O) = default; + + std::optional<CaseType> Case; + std::string Prefix; + std::string Suffix; + // Store both compiled and non-compiled forms so original value can be + // serialized + llvm::Regex IgnoredRegexp; + std::string IgnoredRegexpStr; + + HungarianPrefixType HPType; + }; + + struct HungarianNotation { + public: + bool checkOptionValid(int StyleKindIndex) const; + bool isOptionEnabled(StringRef OptionKey, + const llvm::StringMap<std::string> &StrMap) const; + void loadDefaultConfig( + IdentifierNamingCheck::HungarianNotationOption &HNOption) const; + void loadFileConfig( + const ClangTidyCheck::OptionsView &Options, + IdentifierNamingCheck::HungarianNotationOption &HNOption) const; + + bool removeDuplicatedPrefix( + SmallVector<StringRef, 8> &Words, + const IdentifierNamingCheck::HungarianNotationOption &HNOption) const; + + std::string getPrefix( + const Decl *D, + const IdentifierNamingCheck::HungarianNotationOption &HNOption) const; + + std::string getDataTypePrefix( + StringRef TypeName, const NamedDecl *ND, + const IdentifierNamingCheck::HungarianNotationOption &HNOption) const; + + std::string getClassPrefix( + const CXXRecordDecl *CRD, + const IdentifierNamingCheck::HungarianNotationOption &HNOption) const; + + std::string getEnumPrefix(const EnumConstantDecl *ECD) const; + std::string getDeclTypeName(const NamedDecl *ND) const; + }; + + struct FileStyle { + FileStyle() : IsActive(false), IgnoreMainLikeFunctions(false) {} + FileStyle(SmallVectorImpl<std::optional<NamingStyle>> &&Styles, + HungarianNotationOption HNOption, bool IgnoreMainLike) + : Styles(std::move(Styles)), HNOption(std::move(HNOption)), + IsActive(true), IgnoreMainLikeFunctions(IgnoreMainLike) {} + + ArrayRef<std::optional<NamingStyle>> getStyles() const { + assert(IsActive); + return Styles; + } + + const HungarianNotationOption &getHNOption() const { + assert(IsActive); + return HNOption; + } + + bool isActive() const { return IsActive; } + bool isIgnoringMainLikeFunction() const { return IgnoreMainLikeFunctions; } + + private: + SmallVector<std::optional<NamingStyle>, 0> Styles; + HungarianNotationOption HNOption; + bool IsActive; + bool IgnoreMainLikeFunctions; + }; + + IdentifierNamingCheck::FileStyle + getFileStyleFromOptions(const ClangTidyCheck::OptionsView &Options) const; + + bool + matchesStyle(StringRef Type, StringRef Name, + const IdentifierNamingCheck::NamingStyle &Style, + const IdentifierNamingCheck::HungarianNotationOption &HNOption, + const NamedDecl *Decl) const; + + std::string + fixupWithCase(StringRef Type, StringRef Name, const Decl *D, + const IdentifierNamingCheck::NamingStyle &Style, + const IdentifierNamingCheck::HungarianNotationOption &HNOption, + IdentifierNamingCheck::CaseType Case) const; + + std::string + fixupWithStyle(StringRef Type, StringRef Name, + const IdentifierNamingCheck::NamingStyle &Style, + const IdentifierNamingCheck::HungarianNotationOption &HNOption, + const Decl *D) const; + + StyleKind findStyleKind( + const NamedDecl *D, + ArrayRef<std::optional<IdentifierNamingCheck::NamingStyle>> NamingStyles, + bool IgnoreMainLikeFunctions) const; + + std::optional<RenamerClangTidyCheck::FailureInfo> getFailureInfo( + StringRef Type, StringRef Name, const NamedDecl *ND, + SourceLocation Location, + ArrayRef<std::optional<IdentifierNamingCheck::NamingStyle>> NamingStyles, + const IdentifierNamingCheck::HungarianNotationOption &HNOption, + StyleKind SK, const SourceManager &SM, bool IgnoreFailedSplit) const; + + bool isParamInMainLikeFunction(const ParmVarDecl &ParmDecl, + bool IncludeMainLike) const; + +private: + std::optional<FailureInfo> + getDeclFailureInfo(const NamedDecl *Decl, + const SourceManager &SM) const override; + std::optional<FailureInfo> + getMacroFailureInfo(const Token &MacroNameTok, + const SourceManager &SM) const override; + DiagInfo getDiagInfo(const NamingCheckId &ID, + const NamingCheckFailure &Failure) const override; + + const FileStyle &getStyleForFile(StringRef FileName) const; + + /// Stores the style options as a vector, indexed by the specified \ref + /// StyleKind, for a given directory. + mutable llvm::StringMap<FileStyle> NamingStylesCache; + FileStyle *MainFileStyle; + ClangTidyContext *Context; + const StringRef CheckName; + const bool GetConfigPerFile; + const bool IgnoreFailedSplit; + HungarianNotation HungarianNotation; +}; + +} // namespace readability +template <> +struct OptionEnumMapping<readability::IdentifierNamingCheck::CaseType> { + static llvm::ArrayRef< + std::pair<readability::IdentifierNamingCheck::CaseType, StringRef>> + getEnumMapping(); +}; +} // namespace clang::tidy + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IDENTIFIERNAMINGCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ImplicitBoolConversionCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ImplicitBoolConversionCheck.cpp new file mode 100644 index 0000000000..7ec63d0fd9 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ImplicitBoolConversionCheck.cpp @@ -0,0 +1,392 @@ +//===--- ImplicitBoolConversionCheck.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 "ImplicitBoolConversionCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/FixIt.h" +#include <queue> + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +namespace { + +AST_MATCHER(Stmt, isMacroExpansion) { + SourceManager &SM = Finder->getASTContext().getSourceManager(); + SourceLocation Loc = Node.getBeginLoc(); + return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc); +} + +bool isNULLMacroExpansion(const Stmt *Statement, ASTContext &Context) { + SourceManager &SM = Context.getSourceManager(); + const LangOptions &LO = Context.getLangOpts(); + SourceLocation Loc = Statement->getBeginLoc(); + return SM.isMacroBodyExpansion(Loc) && + Lexer::getImmediateMacroName(Loc, SM, LO) == "NULL"; +} + +AST_MATCHER(Stmt, isNULLMacroExpansion) { + return isNULLMacroExpansion(&Node, Finder->getASTContext()); +} + +StringRef getZeroLiteralToCompareWithForType(CastKind CastExprKind, + QualType Type, + ASTContext &Context) { + switch (CastExprKind) { + case CK_IntegralToBoolean: + return Type->isUnsignedIntegerType() ? "0u" : "0"; + + case CK_FloatingToBoolean: + return Context.hasSameType(Type, Context.FloatTy) ? "0.0f" : "0.0"; + + case CK_PointerToBoolean: + case CK_MemberPointerToBoolean: // Fall-through on purpose. + return Context.getLangOpts().CPlusPlus11 ? "nullptr" : "0"; + + default: + llvm_unreachable("Unexpected cast kind"); + } +} + +bool isUnaryLogicalNotOperator(const Stmt *Statement) { + const auto *UnaryOperatorExpr = dyn_cast<UnaryOperator>(Statement); + return UnaryOperatorExpr && UnaryOperatorExpr->getOpcode() == UO_LNot; +} + +bool areParensNeededForOverloadedOperator(OverloadedOperatorKind OperatorKind) { + switch (OperatorKind) { + case OO_New: + case OO_Delete: // Fall-through on purpose. + case OO_Array_New: + case OO_Array_Delete: + case OO_ArrowStar: + case OO_Arrow: + case OO_Call: + case OO_Subscript: + return false; + + default: + return true; + } +} + +bool areParensNeededForStatement(const Stmt *Statement) { + if (const auto *OperatorCall = dyn_cast<CXXOperatorCallExpr>(Statement)) { + return areParensNeededForOverloadedOperator(OperatorCall->getOperator()); + } + + return isa<BinaryOperator>(Statement) || isa<UnaryOperator>(Statement); +} + +void fixGenericExprCastToBool(DiagnosticBuilder &Diag, + const ImplicitCastExpr *Cast, const Stmt *Parent, + ASTContext &Context) { + // In case of expressions like (! integer), we should remove the redundant not + // operator and use inverted comparison (integer == 0). + bool InvertComparison = + Parent != nullptr && isUnaryLogicalNotOperator(Parent); + if (InvertComparison) { + SourceLocation ParentStartLoc = Parent->getBeginLoc(); + SourceLocation ParentEndLoc = + cast<UnaryOperator>(Parent)->getSubExpr()->getBeginLoc(); + Diag << FixItHint::CreateRemoval( + CharSourceRange::getCharRange(ParentStartLoc, ParentEndLoc)); + + Parent = Context.getParents(*Parent)[0].get<Stmt>(); + } + + const Expr *SubExpr = Cast->getSubExpr(); + + bool NeedInnerParens = areParensNeededForStatement(SubExpr); + bool NeedOuterParens = + Parent != nullptr && areParensNeededForStatement(Parent); + + std::string StartLocInsertion; + + if (NeedOuterParens) { + StartLocInsertion += "("; + } + if (NeedInnerParens) { + StartLocInsertion += "("; + } + + if (!StartLocInsertion.empty()) { + Diag << FixItHint::CreateInsertion(Cast->getBeginLoc(), StartLocInsertion); + } + + std::string EndLocInsertion; + + if (NeedInnerParens) { + EndLocInsertion += ")"; + } + + if (InvertComparison) { + EndLocInsertion += " == "; + } else { + EndLocInsertion += " != "; + } + + EndLocInsertion += getZeroLiteralToCompareWithForType( + Cast->getCastKind(), SubExpr->getType(), Context); + + if (NeedOuterParens) { + EndLocInsertion += ")"; + } + + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Cast->getEndLoc(), 0, Context.getSourceManager(), Context.getLangOpts()); + Diag << FixItHint::CreateInsertion(EndLoc, EndLocInsertion); +} + +StringRef getEquivalentBoolLiteralForExpr(const Expr *Expression, + ASTContext &Context) { + if (isNULLMacroExpansion(Expression, Context)) { + return "false"; + } + + if (const auto *IntLit = dyn_cast<IntegerLiteral>(Expression)) { + return (IntLit->getValue() == 0) ? "false" : "true"; + } + + if (const auto *FloatLit = dyn_cast<FloatingLiteral>(Expression)) { + llvm::APFloat FloatLitAbsValue = FloatLit->getValue(); + FloatLitAbsValue.clearSign(); + return (FloatLitAbsValue.bitcastToAPInt() == 0) ? "false" : "true"; + } + + if (const auto *CharLit = dyn_cast<CharacterLiteral>(Expression)) { + return (CharLit->getValue() == 0) ? "false" : "true"; + } + + if (isa<StringLiteral>(Expression->IgnoreCasts())) { + return "true"; + } + + return StringRef(); +} + +void fixGenericExprCastFromBool(DiagnosticBuilder &Diag, + const ImplicitCastExpr *Cast, + ASTContext &Context, StringRef OtherType) { + const Expr *SubExpr = Cast->getSubExpr(); + bool NeedParens = !isa<ParenExpr>(SubExpr); + + Diag << FixItHint::CreateInsertion( + Cast->getBeginLoc(), + (Twine("static_cast<") + OtherType + ">" + (NeedParens ? "(" : "")) + .str()); + + if (NeedParens) { + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Cast->getEndLoc(), 0, Context.getSourceManager(), + Context.getLangOpts()); + + Diag << FixItHint::CreateInsertion(EndLoc, ")"); + } +} + +StringRef getEquivalentForBoolLiteral(const CXXBoolLiteralExpr *BoolLiteral, + QualType DestType, ASTContext &Context) { + // Prior to C++11, false literal could be implicitly converted to pointer. + if (!Context.getLangOpts().CPlusPlus11 && + (DestType->isPointerType() || DestType->isMemberPointerType()) && + BoolLiteral->getValue() == false) { + return "0"; + } + + if (DestType->isFloatingType()) { + if (Context.hasSameType(DestType, Context.FloatTy)) { + return BoolLiteral->getValue() ? "1.0f" : "0.0f"; + } + return BoolLiteral->getValue() ? "1.0" : "0.0"; + } + + if (DestType->isUnsignedIntegerType()) { + return BoolLiteral->getValue() ? "1u" : "0u"; + } + return BoolLiteral->getValue() ? "1" : "0"; +} + +bool isCastAllowedInCondition(const ImplicitCastExpr *Cast, + ASTContext &Context) { + std::queue<const Stmt *> Q; + Q.push(Cast); + + TraversalKindScope RAII(Context, TK_AsIs); + + while (!Q.empty()) { + for (const auto &N : Context.getParents(*Q.front())) { + const Stmt *S = N.get<Stmt>(); + if (!S) + return false; + if (isa<IfStmt>(S) || isa<ConditionalOperator>(S) || isa<ForStmt>(S) || + isa<WhileStmt>(S) || isa<BinaryConditionalOperator>(S)) + return true; + if (isa<ParenExpr>(S) || isa<ImplicitCastExpr>(S) || + isUnaryLogicalNotOperator(S) || + (isa<BinaryOperator>(S) && cast<BinaryOperator>(S)->isLogicalOp())) { + Q.push(S); + } else { + return false; + } + } + Q.pop(); + } + return false; +} + +} // anonymous namespace + +ImplicitBoolConversionCheck::ImplicitBoolConversionCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + AllowIntegerConditions(Options.get("AllowIntegerConditions", false)), + AllowPointerConditions(Options.get("AllowPointerConditions", false)) {} + +void ImplicitBoolConversionCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "AllowIntegerConditions", AllowIntegerConditions); + Options.store(Opts, "AllowPointerConditions", AllowPointerConditions); +} + +void ImplicitBoolConversionCheck::registerMatchers(MatchFinder *Finder) { + auto ExceptionCases = + expr(anyOf(allOf(isMacroExpansion(), unless(isNULLMacroExpansion())), + has(ignoringImplicit( + memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1)))))), + hasParent(explicitCastExpr()))); + auto ImplicitCastFromBool = implicitCastExpr( + anyOf(hasCastKind(CK_IntegralCast), hasCastKind(CK_IntegralToFloating), + // Prior to C++11 cast from bool literal to pointer was allowed. + allOf(anyOf(hasCastKind(CK_NullToPointer), + hasCastKind(CK_NullToMemberPointer)), + hasSourceExpression(cxxBoolLiteral()))), + hasSourceExpression(expr(hasType(booleanType()))), + unless(ExceptionCases)); + auto BoolXor = + binaryOperator(hasOperatorName("^"), hasLHS(ImplicitCastFromBool), + hasRHS(ImplicitCastFromBool)); + Finder->addMatcher( + traverse(TK_AsIs, + implicitCastExpr( + anyOf(hasCastKind(CK_IntegralToBoolean), + hasCastKind(CK_FloatingToBoolean), + hasCastKind(CK_PointerToBoolean), + hasCastKind(CK_MemberPointerToBoolean)), + // Exclude case of using if or while statements with variable + // declaration, e.g.: + // if (int var = functionCall()) {} + unless(hasParent( + stmt(anyOf(ifStmt(), whileStmt()), has(declStmt())))), + // Exclude cases common to implicit cast to and from bool. + unless(ExceptionCases), unless(has(BoolXor)), + // Retrieve also parent statement, to check if we need + // additional parens in replacement. + anyOf(hasParent(stmt().bind("parentStmt")), anything()), + unless(isInTemplateInstantiation()), + unless(hasAncestor(functionTemplateDecl()))) + .bind("implicitCastToBool")), + this); + + auto BoolComparison = binaryOperator(hasAnyOperatorName("==", "!="), + hasLHS(ImplicitCastFromBool), + hasRHS(ImplicitCastFromBool)); + auto BoolOpAssignment = binaryOperator(hasAnyOperatorName("|=", "&="), + hasLHS(expr(hasType(booleanType())))); + auto BitfieldAssignment = binaryOperator( + hasLHS(memberExpr(hasDeclaration(fieldDecl(hasBitWidth(1)))))); + auto BitfieldConstruct = cxxConstructorDecl(hasDescendant(cxxCtorInitializer( + withInitializer(equalsBoundNode("implicitCastFromBool")), + forField(hasBitWidth(1))))); + Finder->addMatcher( + traverse( + TK_AsIs, + implicitCastExpr( + ImplicitCastFromBool, + // Exclude comparisons of bools, as they are always cast to + // integers in such context: + // bool_expr_a == bool_expr_b + // bool_expr_a != bool_expr_b + unless(hasParent( + binaryOperator(anyOf(BoolComparison, BoolXor, + BoolOpAssignment, BitfieldAssignment)))), + implicitCastExpr().bind("implicitCastFromBool"), + unless(hasParent(BitfieldConstruct)), + // Check also for nested casts, for example: bool -> int -> float. + anyOf(hasParent(implicitCastExpr().bind("furtherImplicitCast")), + anything()), + unless(isInTemplateInstantiation()), + unless(hasAncestor(functionTemplateDecl())))), + this); +} + +void ImplicitBoolConversionCheck::check( + const MatchFinder::MatchResult &Result) { + + if (const auto *CastToBool = + Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastToBool")) { + const auto *Parent = Result.Nodes.getNodeAs<Stmt>("parentStmt"); + return handleCastToBool(CastToBool, Parent, *Result.Context); + } + + if (const auto *CastFromBool = + Result.Nodes.getNodeAs<ImplicitCastExpr>("implicitCastFromBool")) { + const auto *NextImplicitCast = + Result.Nodes.getNodeAs<ImplicitCastExpr>("furtherImplicitCast"); + return handleCastFromBool(CastFromBool, NextImplicitCast, *Result.Context); + } +} + +void ImplicitBoolConversionCheck::handleCastToBool(const ImplicitCastExpr *Cast, + const Stmt *Parent, + ASTContext &Context) { + if (AllowPointerConditions && + (Cast->getCastKind() == CK_PointerToBoolean || + Cast->getCastKind() == CK_MemberPointerToBoolean) && + isCastAllowedInCondition(Cast, Context)) { + return; + } + + if (AllowIntegerConditions && Cast->getCastKind() == CK_IntegralToBoolean && + isCastAllowedInCondition(Cast, Context)) { + return; + } + + auto Diag = diag(Cast->getBeginLoc(), "implicit conversion %0 -> bool") + << Cast->getSubExpr()->getType(); + + StringRef EquivalentLiteral = + getEquivalentBoolLiteralForExpr(Cast->getSubExpr(), Context); + if (!EquivalentLiteral.empty()) { + Diag << tooling::fixit::createReplacement(*Cast, EquivalentLiteral); + } else { + fixGenericExprCastToBool(Diag, Cast, Parent, Context); + } +} + +void ImplicitBoolConversionCheck::handleCastFromBool( + const ImplicitCastExpr *Cast, const ImplicitCastExpr *NextImplicitCast, + ASTContext &Context) { + QualType DestType = + NextImplicitCast ? NextImplicitCast->getType() : Cast->getType(); + auto Diag = diag(Cast->getBeginLoc(), "implicit conversion bool -> %0") + << DestType; + + if (const auto *BoolLiteral = + dyn_cast<CXXBoolLiteralExpr>(Cast->getSubExpr())) { + Diag << tooling::fixit::createReplacement( + *Cast, getEquivalentForBoolLiteral(BoolLiteral, DestType, Context)); + } else { + fixGenericExprCastFromBool(Diag, Cast, Context, DestType.getAsString()); + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ImplicitBoolConversionCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ImplicitBoolConversionCheck.h new file mode 100644 index 0000000000..67fd3ff5c3 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ImplicitBoolConversionCheck.h @@ -0,0 +1,43 @@ +//===--- ImplicitBoolConversionCheck.h - clang-tidy--------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CONVERSION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CONVERSION_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Checks for use of implicit bool conversions in expressions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/implicit-bool-conversion.html +class ImplicitBoolConversionCheck : public ClangTidyCheck { +public: + ImplicitBoolConversionCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.Bool; + } + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void handleCastToBool(const ImplicitCastExpr *CastExpression, + const Stmt *ParentStatement, ASTContext &Context); + void handleCastFromBool(const ImplicitCastExpr *CastExpression, + const ImplicitCastExpr *FurtherImplicitCastExpression, + ASTContext &Context); + + const bool AllowIntegerConditions; + const bool AllowPointerConditions; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_IMPLICIT_BOOL_CONVERSION_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp new file mode 100644 index 0000000000..13031e7cc1 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.cpp @@ -0,0 +1,351 @@ +//===--- InconsistentDeclarationParameterNameCheck.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 "InconsistentDeclarationParameterNameCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/STLExtras.h" + +#include <functional> + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +namespace { + +AST_MATCHER(FunctionDecl, hasOtherDeclarations) { + auto It = Node.redecls_begin(); + auto EndIt = Node.redecls_end(); + + if (It == EndIt) + return false; + + ++It; + return It != EndIt; +} + +struct DifferingParamInfo { + DifferingParamInfo(StringRef SourceName, StringRef OtherName, + SourceRange OtherNameRange, bool GenerateFixItHint) + : SourceName(SourceName), OtherName(OtherName), + OtherNameRange(OtherNameRange), GenerateFixItHint(GenerateFixItHint) {} + + StringRef SourceName; + StringRef OtherName; + SourceRange OtherNameRange; + bool GenerateFixItHint; +}; + +using DifferingParamsContainer = llvm::SmallVector<DifferingParamInfo, 10>; + +struct InconsistentDeclarationInfo { + InconsistentDeclarationInfo(SourceLocation DeclarationLocation, + DifferingParamsContainer &&DifferingParams) + : DeclarationLocation(DeclarationLocation), + DifferingParams(std::move(DifferingParams)) {} + + SourceLocation DeclarationLocation; + DifferingParamsContainer DifferingParams; +}; + +using InconsistentDeclarationsContainer = + llvm::SmallVector<InconsistentDeclarationInfo, 2>; + +bool checkIfFixItHintIsApplicable( + const FunctionDecl *ParameterSourceDeclaration, + const ParmVarDecl *SourceParam, const FunctionDecl *OriginalDeclaration) { + // Assumptions with regard to function declarations/definition: + // * If both function declaration and definition are seen, assume that + // definition is most up-to-date, and use it to generate replacements. + // * If only function declarations are seen, there is no easy way to tell + // which is up-to-date and which is not, so don't do anything. + // TODO: This may be changed later, but for now it seems the reasonable + // solution. + if (!ParameterSourceDeclaration->isThisDeclarationADefinition()) + return false; + + // Assumption: if parameter is not referenced in function definition body, it + // may indicate that it's outdated, so don't touch it. + if (!SourceParam->isReferenced()) + return false; + + // In case there is the primary template definition and (possibly several) + // template specializations (and each with possibly several redeclarations), + // it is not at all clear what to change. + if (OriginalDeclaration->getTemplatedKind() == + FunctionDecl::TK_FunctionTemplateSpecialization) + return false; + + // Other cases seem OK to allow replacements. + return true; +} + +bool nameMatch(StringRef L, StringRef R, bool Strict) { + if (Strict) + return L.empty() || R.empty() || L == R; + // We allow two names if one is a prefix/suffix of the other, ignoring case. + // Important special case: this is true if either parameter has no name! + return L.startswith_insensitive(R) || R.startswith_insensitive(L) || + L.endswith_insensitive(R) || R.endswith_insensitive(L); +} + +DifferingParamsContainer +findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration, + const FunctionDecl *OtherDeclaration, + const FunctionDecl *OriginalDeclaration, + bool Strict) { + DifferingParamsContainer DifferingParams; + + const auto *SourceParamIt = ParameterSourceDeclaration->param_begin(); + const auto *OtherParamIt = OtherDeclaration->param_begin(); + + while (SourceParamIt != ParameterSourceDeclaration->param_end() && + OtherParamIt != OtherDeclaration->param_end()) { + auto SourceParamName = (*SourceParamIt)->getName(); + auto OtherParamName = (*OtherParamIt)->getName(); + + // FIXME: Provide a way to extract commented out parameter name from comment + // next to it. + if (!nameMatch(SourceParamName, OtherParamName, Strict)) { + SourceRange OtherParamNameRange = + DeclarationNameInfo((*OtherParamIt)->getDeclName(), + (*OtherParamIt)->getLocation()) + .getSourceRange(); + + bool GenerateFixItHint = checkIfFixItHintIsApplicable( + ParameterSourceDeclaration, *SourceParamIt, OriginalDeclaration); + + DifferingParams.emplace_back(SourceParamName, OtherParamName, + OtherParamNameRange, GenerateFixItHint); + } + + ++SourceParamIt; + ++OtherParamIt; + } + + return DifferingParams; +} + +InconsistentDeclarationsContainer +findInconsistentDeclarations(const FunctionDecl *OriginalDeclaration, + const FunctionDecl *ParameterSourceDeclaration, + SourceManager &SM, bool Strict) { + InconsistentDeclarationsContainer InconsistentDeclarations; + SourceLocation ParameterSourceLocation = + ParameterSourceDeclaration->getLocation(); + + for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) { + SourceLocation OtherLocation = OtherDeclaration->getLocation(); + if (OtherLocation != ParameterSourceLocation) { // Skip self. + DifferingParamsContainer DifferingParams = + findDifferingParamsInDeclaration(ParameterSourceDeclaration, + OtherDeclaration, + OriginalDeclaration, Strict); + if (!DifferingParams.empty()) { + InconsistentDeclarations.emplace_back(OtherDeclaration->getLocation(), + std::move(DifferingParams)); + } + } + } + + // Sort in order of appearance in translation unit to generate clear + // diagnostics. + llvm::sort(InconsistentDeclarations, + [&SM](const InconsistentDeclarationInfo &Info1, + const InconsistentDeclarationInfo &Info2) { + return SM.isBeforeInTranslationUnit(Info1.DeclarationLocation, + Info2.DeclarationLocation); + }); + return InconsistentDeclarations; +} + +const FunctionDecl * +getParameterSourceDeclaration(const FunctionDecl *OriginalDeclaration) { + const FunctionTemplateDecl *PrimaryTemplate = + OriginalDeclaration->getPrimaryTemplate(); + if (PrimaryTemplate != nullptr) { + // In case of template specializations, use primary template declaration as + // the source of parameter names. + return PrimaryTemplate->getTemplatedDecl(); + } + + // In other cases, try to change to function definition, if available. + + if (OriginalDeclaration->isThisDeclarationADefinition()) + return OriginalDeclaration; + + for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) { + if (OtherDeclaration->isThisDeclarationADefinition()) { + return OtherDeclaration; + } + } + + // No definition found, so return original declaration. + return OriginalDeclaration; +} + +std::string joinParameterNames( + const DifferingParamsContainer &DifferingParams, + llvm::function_ref<StringRef(const DifferingParamInfo &)> ChooseParamName) { + llvm::SmallString<40> Str; + bool First = true; + for (const DifferingParamInfo &ParamInfo : DifferingParams) { + if (First) + First = false; + else + Str += ", "; + Str.append({"'", ChooseParamName(ParamInfo), "'"}); + } + return std::string(Str); +} + +void formatDifferingParamsDiagnostic( + InconsistentDeclarationParameterNameCheck *Check, SourceLocation Location, + StringRef OtherDeclarationDescription, + const DifferingParamsContainer &DifferingParams) { + auto ChooseOtherName = [](const DifferingParamInfo &ParamInfo) { + return ParamInfo.OtherName; + }; + auto ChooseSourceName = [](const DifferingParamInfo &ParamInfo) { + return ParamInfo.SourceName; + }; + + auto ParamDiag = + Check->diag(Location, + "differing parameters are named here: (%0), in %1: (%2)", + DiagnosticIDs::Level::Note) + << joinParameterNames(DifferingParams, ChooseOtherName) + << OtherDeclarationDescription + << joinParameterNames(DifferingParams, ChooseSourceName); + + for (const DifferingParamInfo &ParamInfo : DifferingParams) { + if (ParamInfo.GenerateFixItHint) { + ParamDiag << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ParamInfo.OtherNameRange), + ParamInfo.SourceName); + } + } +} + +void formatDiagnosticsForDeclarations( + InconsistentDeclarationParameterNameCheck *Check, + const FunctionDecl *ParameterSourceDeclaration, + const FunctionDecl *OriginalDeclaration, + const InconsistentDeclarationsContainer &InconsistentDeclarations) { + Check->diag( + OriginalDeclaration->getLocation(), + "function %q0 has %1 other declaration%s1 with different parameter names") + << OriginalDeclaration + << static_cast<int>(InconsistentDeclarations.size()); + int Count = 1; + for (const InconsistentDeclarationInfo &InconsistentDeclaration : + InconsistentDeclarations) { + Check->diag(InconsistentDeclaration.DeclarationLocation, + "the %ordinal0 inconsistent declaration seen here", + DiagnosticIDs::Level::Note) + << Count; + + formatDifferingParamsDiagnostic( + Check, InconsistentDeclaration.DeclarationLocation, + "the other declaration", InconsistentDeclaration.DifferingParams); + + ++Count; + } +} + +void formatDiagnostics( + InconsistentDeclarationParameterNameCheck *Check, + const FunctionDecl *ParameterSourceDeclaration, + const FunctionDecl *OriginalDeclaration, + const InconsistentDeclarationsContainer &InconsistentDeclarations, + StringRef FunctionDescription, StringRef ParameterSourceDescription) { + for (const InconsistentDeclarationInfo &InconsistentDeclaration : + InconsistentDeclarations) { + Check->diag(InconsistentDeclaration.DeclarationLocation, + "%0 %q1 has a %2 with different parameter names") + << FunctionDescription << OriginalDeclaration + << ParameterSourceDescription; + + Check->diag(ParameterSourceDeclaration->getLocation(), "the %0 seen here", + DiagnosticIDs::Level::Note) + << ParameterSourceDescription; + + formatDifferingParamsDiagnostic( + Check, InconsistentDeclaration.DeclarationLocation, + ParameterSourceDescription, InconsistentDeclaration.DifferingParams); + } +} + +} // anonymous namespace + +void InconsistentDeclarationParameterNameCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreMacros", IgnoreMacros); + Options.store(Opts, "Strict", Strict); +} + +void InconsistentDeclarationParameterNameCheck::registerMatchers( + MatchFinder *Finder) { + Finder->addMatcher(functionDecl(hasOtherDeclarations()).bind("functionDecl"), + this); +} + +void InconsistentDeclarationParameterNameCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *OriginalDeclaration = + Result.Nodes.getNodeAs<FunctionDecl>("functionDecl"); + + if (VisitedDeclarations.contains(OriginalDeclaration)) + return; // Avoid multiple warnings. + + const FunctionDecl *ParameterSourceDeclaration = + getParameterSourceDeclaration(OriginalDeclaration); + + InconsistentDeclarationsContainer InconsistentDeclarations = + findInconsistentDeclarations(OriginalDeclaration, + ParameterSourceDeclaration, + *Result.SourceManager, Strict); + if (InconsistentDeclarations.empty()) { + // Avoid unnecessary further visits. + markRedeclarationsAsVisited(OriginalDeclaration); + return; + } + + SourceLocation StartLoc = OriginalDeclaration->getBeginLoc(); + if (StartLoc.isMacroID() && IgnoreMacros) { + markRedeclarationsAsVisited(OriginalDeclaration); + return; + } + + if (OriginalDeclaration->getTemplatedKind() == + FunctionDecl::TK_FunctionTemplateSpecialization) { + formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration, + InconsistentDeclarations, + "function template specialization", + "primary template declaration"); + } else if (ParameterSourceDeclaration->isThisDeclarationADefinition()) { + formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration, + InconsistentDeclarations, "function", "definition"); + } else { + formatDiagnosticsForDeclarations(this, ParameterSourceDeclaration, + OriginalDeclaration, + InconsistentDeclarations); + } + + markRedeclarationsAsVisited(OriginalDeclaration); +} + +void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited( + const FunctionDecl *OriginalDeclaration) { + for (const FunctionDecl *Redecl : OriginalDeclaration->redecls()) { + VisitedDeclarations.insert(Redecl); + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h new file mode 100644 index 0000000000..11fee1e720 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/InconsistentDeclarationParameterNameCheck.h @@ -0,0 +1,48 @@ +//===- InconsistentDeclarationParameterNameCheck.h - clang-tidy-*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_INCONSISTENT_DECLARATION_PARAMETER_NAME_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_INCONSISTENT_DECLARATION_PARAMETER_NAME_H + +#include "../ClangTidyCheck.h" + +#include "llvm/ADT/DenseSet.h" + +namespace clang::tidy::readability { + +/// Checks for declarations of functions which differ in parameter names. +/// +/// For detailed documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/inconsistent-declaration-parameter-name.html +/// +class InconsistentDeclarationParameterNameCheck : public ClangTidyCheck { +public: + InconsistentDeclarationParameterNameCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)), + Strict(Options.getLocalOrGlobal("Strict", false)) {} + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + void markRedeclarationsAsVisited(const FunctionDecl *FunctionDeclaration); + + llvm::DenseSet<const FunctionDecl *> VisitedDeclarations; + const bool IgnoreMacros; + const bool Strict; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_INCONSISTENT_DECLARATION_PARAMETER_NAME_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/IsolateDeclarationCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/IsolateDeclarationCheck.cpp new file mode 100644 index 0000000000..97abf8d9ec --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/IsolateDeclarationCheck.cpp @@ -0,0 +1,274 @@ +//===--- IsolateDeclarationCheck.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 "IsolateDeclarationCheck.h" +#include "../utils/LexerUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include <optional> + +using namespace clang::ast_matchers; +using namespace clang::tidy::utils::lexer; + +namespace clang::tidy::readability { + +namespace { +AST_MATCHER(DeclStmt, isSingleDecl) { return Node.isSingleDecl(); } +AST_MATCHER(DeclStmt, onlyDeclaresVariables) { + return llvm::all_of(Node.decls(), [](Decl *D) { return isa<VarDecl>(D); }); +} +} // namespace + +void IsolateDeclarationCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(declStmt(onlyDeclaresVariables(), unless(isSingleDecl()), + hasParent(compoundStmt())) + .bind("decl_stmt"), + this); +} + +static SourceLocation findStartOfIndirection(SourceLocation Start, + int Indirections, + const SourceManager &SM, + const LangOptions &LangOpts) { + assert(Indirections >= 0 && "Indirections must be non-negative"); + if (Indirections == 0) + return Start; + + // Note that the post-fix decrement is necessary to perform the correct + // number of transformations. + while (Indirections-- != 0) { + Start = findPreviousAnyTokenKind(Start, SM, LangOpts, tok::star, tok::amp); + if (Start.isInvalid() || Start.isMacroID()) + return SourceLocation(); + } + return Start; +} + +static bool isMacroID(SourceRange R) { + return R.getBegin().isMacroID() || R.getEnd().isMacroID(); +} + +/// This function counts the number of written indirections for the given +/// Type \p T. It does \b NOT resolve typedefs as it's a helper for lexing +/// the source code. +/// \see declRanges +static int countIndirections(const Type *T, int Indirections = 0) { + if (T->isFunctionPointerType()) { + const auto *Pointee = T->getPointeeType()->castAs<FunctionType>(); + return countIndirections( + Pointee->getReturnType().IgnoreParens().getTypePtr(), ++Indirections); + } + + // Note: Do not increment the 'Indirections' because it is not yet clear + // if there is an indirection added in the source code of the array + // declaration. + if (const auto *AT = dyn_cast<ArrayType>(T)) + return countIndirections(AT->getElementType().IgnoreParens().getTypePtr(), + Indirections); + + if (isa<PointerType>(T) || isa<ReferenceType>(T)) + return countIndirections(T->getPointeeType().IgnoreParens().getTypePtr(), + ++Indirections); + + return Indirections; +} + +static bool typeIsMemberPointer(const Type *T) { + if (isa<ArrayType>(T)) + return typeIsMemberPointer(T->getArrayElementTypeNoTypeQual()); + + if ((isa<PointerType>(T) || isa<ReferenceType>(T)) && + isa<PointerType>(T->getPointeeType())) + return typeIsMemberPointer(T->getPointeeType().getTypePtr()); + + return isa<MemberPointerType>(T); +} + +/// This function tries to extract the SourceRanges that make up all +/// declarations in this \c DeclStmt. +/// +/// The resulting vector has the structure {UnderlyingType, Decl1, Decl2, ...}. +/// Each \c SourceRange is of the form [Begin, End). +/// If any of the create ranges is invalid or in a macro the result will be +/// \c None. +/// If the \c DeclStmt contains only one declaration, the result is \c None. +/// If the \c DeclStmt contains declarations other than \c VarDecl the result +/// is \c None. +/// +/// \code +/// int * ptr1 = nullptr, value = 42; +/// // [ ][ ] [ ] - The ranges here are inclusive +/// \endcode +/// \todo Generalize this function to take other declarations than \c VarDecl. +static std::optional<std::vector<SourceRange>> +declRanges(const DeclStmt *DS, const SourceManager &SM, + const LangOptions &LangOpts) { + std::size_t DeclCount = std::distance(DS->decl_begin(), DS->decl_end()); + if (DeclCount < 2) + return std::nullopt; + + if (rangeContainsExpansionsOrDirectives(DS->getSourceRange(), SM, LangOpts)) + return std::nullopt; + + // The initial type of the declaration and each declaration has it's own + // slice. This is necessary, because pointers and references bind only + // to the local variable and not to all variables in the declaration. + // Example: 'int *pointer, value = 42;' + std::vector<SourceRange> Slices; + Slices.reserve(DeclCount + 1); + + // Calculate the first slice, for now only variables are handled but in the + // future this should be relaxed and support various kinds of declarations. + const auto *FirstDecl = dyn_cast<VarDecl>(*DS->decl_begin()); + + if (FirstDecl == nullptr) + return std::nullopt; + + // FIXME: Member pointers are not transformed correctly right now, that's + // why they are treated as problematic here. + if (typeIsMemberPointer(FirstDecl->getType().IgnoreParens().getTypePtr())) + return std::nullopt; + + // Consider the following case: 'int * pointer, value = 42;' + // Created slices (inclusive) [ ][ ] [ ] + // Because 'getBeginLoc' points to the start of the variable *name*, the + // location of the pointer must be determined separately. + SourceLocation Start = findStartOfIndirection( + FirstDecl->getLocation(), + countIndirections(FirstDecl->getType().IgnoreParens().getTypePtr()), SM, + LangOpts); + + // Fix function-pointer declarations that have a '(' in front of the + // pointer. + // Example: 'void (*f2)(int), (*g2)(int, float) = gg;' + // Slices: [ ][ ] [ ] + if (FirstDecl->getType()->isFunctionPointerType()) + Start = findPreviousTokenKind(Start, SM, LangOpts, tok::l_paren); + + // It is possible that a declarator is wrapped with parens. + // Example: 'float (((*f_ptr2)))[42], *f_ptr3, ((f_value2)) = 42.f;' + // The slice for the type-part must not contain these parens. Consequently + // 'Start' is moved to the most left paren if there are parens. + while (true) { + if (Start.isInvalid() || Start.isMacroID()) + break; + + Token T = getPreviousToken(Start, SM, LangOpts); + if (T.is(tok::l_paren)) { + Start = findPreviousTokenStart(Start, SM, LangOpts); + continue; + } + break; + } + + SourceRange DeclRange(DS->getBeginLoc(), Start); + if (DeclRange.isInvalid() || isMacroID(DeclRange)) + return std::nullopt; + + // The first slice, that is prepended to every isolated declaration, is + // created. + Slices.emplace_back(DeclRange); + + // Create all following slices that each declare a variable. + SourceLocation DeclBegin = Start; + for (const auto &Decl : DS->decls()) { + const auto *CurrentDecl = cast<VarDecl>(Decl); + + // FIXME: Member pointers are not transformed correctly right now, that's + // why they are treated as problematic here. + if (typeIsMemberPointer(CurrentDecl->getType().IgnoreParens().getTypePtr())) + return std::nullopt; + + SourceLocation DeclEnd = + CurrentDecl->hasInit() + ? findNextTerminator(CurrentDecl->getInit()->getEndLoc(), SM, + LangOpts) + : findNextTerminator(CurrentDecl->getEndLoc(), SM, LangOpts); + + SourceRange VarNameRange(DeclBegin, DeclEnd); + if (VarNameRange.isInvalid() || isMacroID(VarNameRange)) + return std::nullopt; + + Slices.emplace_back(VarNameRange); + DeclBegin = DeclEnd.getLocWithOffset(1); + } + return Slices; +} + +static std::optional<std::vector<StringRef>> +collectSourceRanges(llvm::ArrayRef<SourceRange> Ranges, const SourceManager &SM, + const LangOptions &LangOpts) { + std::vector<StringRef> Snippets; + Snippets.reserve(Ranges.size()); + + for (const auto &Range : Ranges) { + CharSourceRange CharRange = Lexer::getAsCharRange( + CharSourceRange::getCharRange(Range.getBegin(), Range.getEnd()), SM, + LangOpts); + + if (CharRange.isInvalid()) + return std::nullopt; + + bool InvalidText = false; + StringRef Snippet = + Lexer::getSourceText(CharRange, SM, LangOpts, &InvalidText); + + if (InvalidText) + return std::nullopt; + + Snippets.emplace_back(Snippet); + } + + return Snippets; +} + +/// Expects a vector {TypeSnippet, Firstdecl, SecondDecl, ...}. +static std::vector<std::string> +createIsolatedDecls(llvm::ArrayRef<StringRef> Snippets) { + // The first section is the type snippet, which does not make a decl itself. + assert(Snippets.size() > 2 && "Not enough snippets to create isolated decls"); + std::vector<std::string> Decls(Snippets.size() - 1); + + for (std::size_t I = 1; I < Snippets.size(); ++I) + Decls[I - 1] = Twine(Snippets[0]) + .concat(Snippets[0].endswith(" ") ? "" : " ") + .concat(Snippets[I].ltrim()) + .concat(";") + .str(); + + return Decls; +} + +void IsolateDeclarationCheck::check(const MatchFinder::MatchResult &Result) { + const auto *WholeDecl = Result.Nodes.getNodeAs<DeclStmt>("decl_stmt"); + + auto Diag = + diag(WholeDecl->getBeginLoc(), + "multiple declarations in a single statement reduces readability"); + + std::optional<std::vector<SourceRange>> PotentialRanges = + declRanges(WholeDecl, *Result.SourceManager, getLangOpts()); + if (!PotentialRanges) + return; + + std::optional<std::vector<StringRef>> PotentialSnippets = collectSourceRanges( + *PotentialRanges, *Result.SourceManager, getLangOpts()); + + if (!PotentialSnippets) + return; + + std::vector<std::string> NewDecls = createIsolatedDecls(*PotentialSnippets); + std::string Replacement = llvm::join( + NewDecls, + (Twine("\n") + Lexer::getIndentationForLine(WholeDecl->getBeginLoc(), + *Result.SourceManager)) + .str()); + + Diag << FixItHint::CreateReplacement(WholeDecl->getSourceRange(), + Replacement); +} +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/IsolateDeclarationCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/IsolateDeclarationCheck.h new file mode 100644 index 0000000000..63e37a48ca --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/IsolateDeclarationCheck.h @@ -0,0 +1,31 @@ +//===--- IsolateDeclarationCheck.h - clang-tidy -----------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ISOLATEDECLCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ISOLATEDECLCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// This check diagnoses all DeclStmt's declaring more than one variable and +/// tries to refactor the code to one statement per declaration. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/isolate-declaration.html +class IsolateDeclarationCheck : public ClangTidyCheck { +public: + IsolateDeclarationCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ISOLATEDECLCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/MagicNumbersCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MagicNumbersCheck.cpp new file mode 100644 index 0000000000..64940ede8e --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MagicNumbersCheck.cpp @@ -0,0 +1,228 @@ +//===--- MagicNumbersCheck.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 +// +//===----------------------------------------------------------------------===// +// +// A checker for magic numbers: integer or floating point literals embedded +// in the code, outside the definition of a constant or an enumeration. +// +//===----------------------------------------------------------------------===// + +#include "MagicNumbersCheck.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/STLExtras.h" +#include <algorithm> + +using namespace clang::ast_matchers; + +namespace clang { + +static bool isUsedToInitializeAConstant(const MatchFinder::MatchResult &Result, + const DynTypedNode &Node) { + + const auto *AsDecl = Node.get<DeclaratorDecl>(); + if (AsDecl) { + if (AsDecl->getType().isConstQualified()) + return true; + + return AsDecl->isImplicit(); + } + + if (Node.get<EnumConstantDecl>()) + return true; + + return llvm::any_of(Result.Context->getParents(Node), + [&Result](const DynTypedNode &Parent) { + return isUsedToInitializeAConstant(Result, Parent); + }); +} + +static bool isUsedToDefineABitField(const MatchFinder::MatchResult &Result, + const DynTypedNode &Node) { + const auto *AsFieldDecl = Node.get<FieldDecl>(); + if (AsFieldDecl && AsFieldDecl->isBitField()) + return true; + + return llvm::any_of(Result.Context->getParents(Node), + [&Result](const DynTypedNode &Parent) { + return isUsedToDefineABitField(Result, Parent); + }); +} + +namespace tidy::readability { + +const char DefaultIgnoredIntegerValues[] = "1;2;3;4;"; +const char DefaultIgnoredFloatingPointValues[] = "1.0;100.0;"; + +MagicNumbersCheck::MagicNumbersCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreAllFloatingPointValues( + Options.get("IgnoreAllFloatingPointValues", false)), + IgnoreBitFieldsWidths(Options.get("IgnoreBitFieldsWidths", true)), + IgnorePowersOf2IntegerValues( + Options.get("IgnorePowersOf2IntegerValues", false)), + RawIgnoredIntegerValues( + Options.get("IgnoredIntegerValues", DefaultIgnoredIntegerValues)), + RawIgnoredFloatingPointValues(Options.get( + "IgnoredFloatingPointValues", DefaultIgnoredFloatingPointValues)) { + // Process the set of ignored integer values. + const std::vector<StringRef> IgnoredIntegerValuesInput = + utils::options::parseStringList(RawIgnoredIntegerValues); + IgnoredIntegerValues.resize(IgnoredIntegerValuesInput.size()); + llvm::transform(IgnoredIntegerValuesInput, IgnoredIntegerValues.begin(), + [](StringRef Value) { + int64_t Res; + Value.getAsInteger(10, Res); + return Res; + }); + llvm::sort(IgnoredIntegerValues); + + if (!IgnoreAllFloatingPointValues) { + // Process the set of ignored floating point values. + const std::vector<StringRef> IgnoredFloatingPointValuesInput = + utils::options::parseStringList(RawIgnoredFloatingPointValues); + IgnoredFloatingPointValues.reserve(IgnoredFloatingPointValuesInput.size()); + IgnoredDoublePointValues.reserve(IgnoredFloatingPointValuesInput.size()); + for (const auto &InputValue : IgnoredFloatingPointValuesInput) { + llvm::APFloat FloatValue(llvm::APFloat::IEEEsingle()); + auto StatusOrErr = + FloatValue.convertFromString(InputValue, DefaultRoundingMode); + assert(StatusOrErr && "Invalid floating point representation"); + consumeError(StatusOrErr.takeError()); + IgnoredFloatingPointValues.push_back(FloatValue.convertToFloat()); + + llvm::APFloat DoubleValue(llvm::APFloat::IEEEdouble()); + StatusOrErr = + DoubleValue.convertFromString(InputValue, DefaultRoundingMode); + assert(StatusOrErr && "Invalid floating point representation"); + consumeError(StatusOrErr.takeError()); + IgnoredDoublePointValues.push_back(DoubleValue.convertToDouble()); + } + llvm::sort(IgnoredFloatingPointValues); + llvm::sort(IgnoredDoublePointValues); + } +} + +void MagicNumbersCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreAllFloatingPointValues", + IgnoreAllFloatingPointValues); + Options.store(Opts, "IgnoreBitFieldsWidths", IgnoreBitFieldsWidths); + Options.store(Opts, "IgnorePowersOf2IntegerValues", + IgnorePowersOf2IntegerValues); + Options.store(Opts, "IgnoredIntegerValues", RawIgnoredIntegerValues); + Options.store(Opts, "IgnoredFloatingPointValues", + RawIgnoredFloatingPointValues); +} + +void MagicNumbersCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(integerLiteral().bind("integer"), this); + if (!IgnoreAllFloatingPointValues) + Finder->addMatcher(floatLiteral().bind("float"), this); +} + +void MagicNumbersCheck::check(const MatchFinder::MatchResult &Result) { + + TraversalKindScope RAII(*Result.Context, TK_AsIs); + + checkBoundMatch<IntegerLiteral>(Result, "integer"); + checkBoundMatch<FloatingLiteral>(Result, "float"); +} + +bool MagicNumbersCheck::isConstant(const MatchFinder::MatchResult &Result, + const Expr &ExprResult) const { + return llvm::any_of( + Result.Context->getParents(ExprResult), + [&Result](const DynTypedNode &Parent) { + if (isUsedToInitializeAConstant(Result, Parent)) + return true; + + // Ignore this instance, because this matches an + // expanded class enumeration value. + if (Parent.get<CStyleCastExpr>() && + llvm::any_of( + Result.Context->getParents(Parent), + [](const DynTypedNode &GrandParent) { + return GrandParent.get<SubstNonTypeTemplateParmExpr>() != + nullptr; + })) + return true; + + // Ignore this instance, because this match reports the + // location where the template is defined, not where it + // is instantiated. + if (Parent.get<SubstNonTypeTemplateParmExpr>()) + return true; + + // Don't warn on string user defined literals: + // std::string s = "Hello World"s; + if (const auto *UDL = Parent.get<UserDefinedLiteral>()) + if (UDL->getLiteralOperatorKind() == UserDefinedLiteral::LOK_String) + return true; + + return false; + }); +} + +bool MagicNumbersCheck::isIgnoredValue(const IntegerLiteral *Literal) const { + const llvm::APInt IntValue = Literal->getValue(); + const int64_t Value = IntValue.getZExtValue(); + if (Value == 0) + return true; + + if (IgnorePowersOf2IntegerValues && IntValue.isPowerOf2()) + return true; + + return std::binary_search(IgnoredIntegerValues.begin(), + IgnoredIntegerValues.end(), Value); +} + +bool MagicNumbersCheck::isIgnoredValue(const FloatingLiteral *Literal) const { + const llvm::APFloat FloatValue = Literal->getValue(); + if (FloatValue.isZero()) + return true; + + if (&FloatValue.getSemantics() == &llvm::APFloat::IEEEsingle()) { + const float Value = FloatValue.convertToFloat(); + return std::binary_search(IgnoredFloatingPointValues.begin(), + IgnoredFloatingPointValues.end(), Value); + } + + if (&FloatValue.getSemantics() == &llvm::APFloat::IEEEdouble()) { + const double Value = FloatValue.convertToDouble(); + return std::binary_search(IgnoredDoublePointValues.begin(), + IgnoredDoublePointValues.end(), Value); + } + + return false; +} + +bool MagicNumbersCheck::isSyntheticValue(const SourceManager *SourceManager, + const IntegerLiteral *Literal) const { + const std::pair<FileID, unsigned> FileOffset = + SourceManager->getDecomposedLoc(Literal->getLocation()); + if (FileOffset.first.isInvalid()) + return false; + + const StringRef BufferIdentifier = + SourceManager->getBufferOrFake(FileOffset.first).getBufferIdentifier(); + + return BufferIdentifier.empty(); +} + +bool MagicNumbersCheck::isBitFieldWidth( + const clang::ast_matchers::MatchFinder::MatchResult &Result, + const IntegerLiteral &Literal) const { + return IgnoreBitFieldsWidths && + llvm::any_of(Result.Context->getParents(Literal), + [&Result](const DynTypedNode &Parent) { + return isUsedToDefineABitField(Result, Parent); + }); +} + +} // namespace tidy::readability +} // namespace clang diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/MagicNumbersCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MagicNumbersCheck.h new file mode 100644 index 0000000000..ba39f17f4d --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MagicNumbersCheck.h @@ -0,0 +1,105 @@ +//===--- MagicNumbersCheck.h - clang-tidy-----------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MAGICNUMBERSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MAGICNUMBERSCHECK_H + +#include "../ClangTidyCheck.h" +#include "clang/Lex/Lexer.h" +#include <llvm/ADT/APFloat.h> +#include <llvm/ADT/SmallVector.h> + +namespace clang::tidy::readability { + +/// Detects magic numbers, integer and floating point literals embedded in code. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/magic-numbers.html +class MagicNumbersCheck : public ClangTidyCheck { +public: + MagicNumbersCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + bool isConstant(const clang::ast_matchers::MatchFinder::MatchResult &Result, + const clang::Expr &ExprResult) const; + + bool isIgnoredValue(const IntegerLiteral *Literal) const; + bool isIgnoredValue(const FloatingLiteral *Literal) const; + + bool isSyntheticValue(const clang::SourceManager *, + const FloatingLiteral *) const { + return false; + } + bool isSyntheticValue(const clang::SourceManager *SourceManager, + const IntegerLiteral *Literal) const; + + bool isBitFieldWidth(const clang::ast_matchers::MatchFinder::MatchResult &, + const FloatingLiteral &) const { + return false; + } + + bool isBitFieldWidth(const clang::ast_matchers::MatchFinder::MatchResult &Result, + const IntegerLiteral &Literal) const; + + template <typename L> + void checkBoundMatch(const ast_matchers::MatchFinder::MatchResult &Result, + const char *BoundName) { + const L *MatchedLiteral = Result.Nodes.getNodeAs<L>(BoundName); + if (!MatchedLiteral) + return; + + if (Result.SourceManager->isMacroBodyExpansion( + MatchedLiteral->getLocation())) + return; + + if (isIgnoredValue(MatchedLiteral)) + return; + + if (isConstant(Result, *MatchedLiteral)) + return; + + if (isSyntheticValue(Result.SourceManager, MatchedLiteral)) + return; + + if (isBitFieldWidth(Result, *MatchedLiteral)) + return; + + const StringRef LiteralSourceText = Lexer::getSourceText( + CharSourceRange::getTokenRange(MatchedLiteral->getSourceRange()), + *Result.SourceManager, getLangOpts()); + + diag(MatchedLiteral->getLocation(), + "%0 is a magic number; consider replacing it with a named constant") + << LiteralSourceText; + } + + const bool IgnoreAllFloatingPointValues; + const bool IgnoreBitFieldsWidths; + const bool IgnorePowersOf2IntegerValues; + const StringRef RawIgnoredIntegerValues; + const StringRef RawIgnoredFloatingPointValues; + + constexpr static unsigned SensibleNumberOfMagicValueExceptions = 16; + + constexpr static llvm::APFloat::roundingMode DefaultRoundingMode = + llvm::APFloat::rmNearestTiesToEven; + + llvm::SmallVector<int64_t, SensibleNumberOfMagicValueExceptions> + IgnoredIntegerValues; + llvm::SmallVector<float, SensibleNumberOfMagicValueExceptions> + IgnoredFloatingPointValues; + llvm::SmallVector<double, SensibleNumberOfMagicValueExceptions> + IgnoredDoublePointValues; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MAGICNUMBERSCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp new file mode 100644 index 0000000000..2d6a469252 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp @@ -0,0 +1,268 @@ +//===--- MakeMemberFunctionConstCheck.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 "MakeMemberFunctionConstCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ParentMapContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } + +AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); } + +AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) { + return Node.hasAnyDependentBases(); +} + +AST_MATCHER(CXXMethodDecl, isTemplate) { + return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate; +} + +AST_MATCHER(CXXMethodDecl, isDependentContext) { + return Node.isDependentContext(); +} + +AST_MATCHER(CXXMethodDecl, isInsideMacroDefinition) { + const ASTContext &Ctxt = Finder->getASTContext(); + return clang::Lexer::makeFileCharRange( + clang::CharSourceRange::getCharRange( + Node.getTypeSourceInfo()->getTypeLoc().getSourceRange()), + Ctxt.getSourceManager(), Ctxt.getLangOpts()) + .isInvalid(); +} + +AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl, + ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) { + return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder); +} + +enum UsageKind { Unused, Const, NonConst }; + +class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> { + ASTContext &Ctxt; + +public: + FindUsageOfThis(ASTContext &Ctxt) : Ctxt(Ctxt) {} + UsageKind Usage = Unused; + + template <class T> const T *getParent(const Expr *E) { + DynTypedNodeList Parents = Ctxt.getParents(*E); + if (Parents.size() != 1) + return nullptr; + + return Parents.begin()->get<T>(); + } + + const Expr *getParentExprIgnoreParens(const Expr *E) { + const Expr *Parent = getParent<Expr>(E); + while (isa_and_nonnull<ParenExpr>(Parent)) + Parent = getParent<Expr>(Parent); + return Parent; + } + + bool VisitUnresolvedMemberExpr(const UnresolvedMemberExpr *) { + // An UnresolvedMemberExpr might resolve to a non-const non-static + // member function. + Usage = NonConst; + return false; // Stop traversal. + } + + bool VisitCXXConstCastExpr(const CXXConstCastExpr *) { + // Workaround to support the pattern + // class C { + // const S *get() const; + // S* get() { + // return const_cast<S*>(const_cast<const C*>(this)->get()); + // } + // }; + // Here, we don't want to make the second 'get' const even though + // it only calls a const member function on this. + Usage = NonConst; + return false; // Stop traversal. + } + + // Our AST is + // `-ImplicitCastExpr + // (possibly `-UnaryOperator Deref) + // `-CXXThisExpr 'S *' this + bool visitUser(const ImplicitCastExpr *Cast) { + if (Cast->getCastKind() != CK_NoOp) + return false; // Stop traversal. + + // Only allow NoOp cast to 'const S' or 'const S *'. + QualType QT = Cast->getType(); + if (QT->isPointerType()) + QT = QT->getPointeeType(); + + if (!QT.isConstQualified()) + return false; // Stop traversal. + + const auto *Parent = getParent<Stmt>(Cast); + if (!Parent) + return false; // Stop traversal. + + if (isa<ReturnStmt>(Parent)) + return true; // return (const S*)this; + + if (isa<CallExpr>(Parent)) + return true; // use((const S*)this); + + // ((const S*)this)->Member + if (const auto *Member = dyn_cast<MemberExpr>(Parent)) + return visitUser(Member, /*OnConstObject=*/true); + + return false; // Stop traversal. + } + + // If OnConstObject is true, then this is a MemberExpr using + // a constant this, i.e. 'const S' or 'const S *'. + bool visitUser(const MemberExpr *Member, bool OnConstObject) { + if (Member->isBoundMemberFunction(Ctxt)) { + if (!OnConstObject || Member->getFoundDecl().getAccess() != AS_public) { + // Non-public non-static member functions might not preserve the + // logical constness. E.g. in + // class C { + // int &data() const; + // public: + // int &get() { return data(); } + // }; + // get() uses a private const method, but must not be made const + // itself. + return false; // Stop traversal. + } + // Using a public non-static const member function. + return true; + } + + const auto *Parent = getParentExprIgnoreParens(Member); + + if (const auto *Cast = dyn_cast_or_null<ImplicitCastExpr>(Parent)) { + // A read access to a member is safe when the member either + // 1) has builtin type (a 'const int' cannot be modified), + // 2) or it's a public member (the pointee of a public 'int * const' can + // can be modified by any user of the class). + if (Member->getFoundDecl().getAccess() != AS_public && + !Cast->getType()->isBuiltinType()) + return false; + + if (Cast->getCastKind() == CK_LValueToRValue) + return true; + + if (Cast->getCastKind() == CK_NoOp && Cast->getType().isConstQualified()) + return true; + } + + if (const auto *M = dyn_cast_or_null<MemberExpr>(Parent)) + return visitUser(M, /*OnConstObject=*/false); + + return false; // Stop traversal. + } + + bool VisitCXXThisExpr(const CXXThisExpr *E) { + Usage = Const; + + const auto *Parent = getParentExprIgnoreParens(E); + + // Look through deref of this. + if (const auto *UnOp = dyn_cast_or_null<UnaryOperator>(Parent)) { + if (UnOp->getOpcode() == UO_Deref) { + Parent = getParentExprIgnoreParens(UnOp); + } + } + + // It's okay to + // return (const S*)this; + // use((const S*)this); + // ((const S*)this)->f() + // when 'f' is a public member function. + if (const auto *Cast = dyn_cast_or_null<ImplicitCastExpr>(Parent)) { + if (visitUser(Cast)) + return true; + + // And it's also okay to + // (const T)(S->t) + // (LValueToRValue)(S->t) + // when 't' is either of builtin type or a public member. + } else if (const auto *Member = dyn_cast_or_null<MemberExpr>(Parent)) { + if (visitUser(Member, /*OnConstObject=*/false)) + return true; + } + + // Unknown user of this. + Usage = NonConst; + return false; // Stop traversal. + } +}; + +AST_MATCHER(CXXMethodDecl, usesThisAsConst) { + FindUsageOfThis UsageOfThis(Finder->getASTContext()); + + // TraverseStmt does not modify its argument. + UsageOfThis.TraverseStmt(const_cast<Stmt *>(Node.getBody())); + + return UsageOfThis.Usage == Const; +} + +void MakeMemberFunctionConstCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + traverse( + TK_AsIs, + cxxMethodDecl( + isDefinition(), isUserProvided(), + unless(anyOf( + isExpansionInSystemHeader(), isVirtual(), isConst(), + isStatic(), hasTrivialBody(), cxxConstructorDecl(), + cxxDestructorDecl(), isTemplate(), isDependentContext(), + ofClass(anyOf(isLambda(), + hasAnyDependentBases()) // Method might become + // virtual depending on + // template base class. + ), + isInsideMacroDefinition(), + hasCanonicalDecl(isInsideMacroDefinition()))), + usesThisAsConst()) + .bind("x")), + this); +} + +static SourceLocation getConstInsertionPoint(const CXXMethodDecl *M) { + TypeSourceInfo *TSI = M->getTypeSourceInfo(); + if (!TSI) + return {}; + + FunctionTypeLoc FTL = + TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>(); + if (!FTL) + return {}; + + return FTL.getRParenLoc().getLocWithOffset(1); +} + +void MakeMemberFunctionConstCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>("x"); + + const auto *Declaration = Definition->getCanonicalDecl(); + + auto Diag = diag(Definition->getLocation(), "method %0 can be made const") + << Definition + << FixItHint::CreateInsertion(getConstInsertionPoint(Definition), + " const"); + if (Declaration != Definition) { + Diag << FixItHint::CreateInsertion(getConstInsertionPoint(Declaration), + " const"); + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/MakeMemberFunctionConstCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MakeMemberFunctionConstCheck.h new file mode 100644 index 0000000000..dc8d983327 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MakeMemberFunctionConstCheck.h @@ -0,0 +1,33 @@ +//===--- MakeMemberFunctionConstCheck.h - clang-tidy ------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MAKEMEMBERFUNCTIONCONSTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MAKEMEMBERFUNCTIONCONSTCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Finds non-static member functions that can be made 'const'. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/make-member-function-const.html +class MakeMemberFunctionConstCheck : public ClangTidyCheck { +public: + MakeMemberFunctionConstCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MAKEMEMBERFUNCTIONCONSTCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/MisleadingIndentationCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MisleadingIndentationCheck.cpp new file mode 100644 index 0000000000..4c10b6f6ee --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MisleadingIndentationCheck.cpp @@ -0,0 +1,122 @@ +//===--- MisleadingIndentationCheck.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 "MisleadingIndentationCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +static const IfStmt *getPrecedingIf(const SourceManager &SM, + ASTContext *Context, const IfStmt *If) { + auto Parents = Context->getParents(*If); + if (Parents.size() != 1) + return nullptr; + if (const auto *PrecedingIf = Parents[0].get<IfStmt>()) { + SourceLocation PreviousElseLoc = PrecedingIf->getElseLoc(); + if (SM.getExpansionLineNumber(PreviousElseLoc) == + SM.getExpansionLineNumber(If->getIfLoc())) + return PrecedingIf; + } + return nullptr; +} + +void MisleadingIndentationCheck::danglingElseCheck(const SourceManager &SM, + ASTContext *Context, + const IfStmt *If) { + SourceLocation IfLoc = If->getIfLoc(); + SourceLocation ElseLoc = If->getElseLoc(); + + if (IfLoc.isMacroID() || ElseLoc.isMacroID()) + return; + + if (SM.getExpansionLineNumber(If->getThen()->getEndLoc()) == + SM.getExpansionLineNumber(ElseLoc)) + return; + + // Find location of first 'if' in a 'if else if' chain. + for (const auto *PrecedingIf = getPrecedingIf(SM, Context, If); PrecedingIf; + PrecedingIf = getPrecedingIf(SM, Context, PrecedingIf)) + IfLoc = PrecedingIf->getIfLoc(); + + if (SM.getExpansionColumnNumber(IfLoc) != + SM.getExpansionColumnNumber(ElseLoc)) + diag(ElseLoc, "different indentation for 'if' and corresponding 'else'"); +} + +void MisleadingIndentationCheck::missingBracesCheck(const SourceManager &SM, + const CompoundStmt *CStmt) { + const static StringRef StmtNames[] = {"if", "for", "while"}; + for (unsigned int I = 0; I < CStmt->size() - 1; I++) { + const Stmt *CurrentStmt = CStmt->body_begin()[I]; + const Stmt *Inner = nullptr; + int StmtKind = 0; + + if (const auto *CurrentIf = dyn_cast<IfStmt>(CurrentStmt)) { + StmtKind = 0; + Inner = + CurrentIf->getElse() ? CurrentIf->getElse() : CurrentIf->getThen(); + } else if (const auto *CurrentFor = dyn_cast<ForStmt>(CurrentStmt)) { + StmtKind = 1; + Inner = CurrentFor->getBody(); + } else if (const auto *CurrentWhile = dyn_cast<WhileStmt>(CurrentStmt)) { + StmtKind = 2; + Inner = CurrentWhile->getBody(); + } else { + continue; + } + + if (isa<CompoundStmt>(Inner)) + continue; + + SourceLocation InnerLoc = Inner->getBeginLoc(); + SourceLocation OuterLoc = CurrentStmt->getBeginLoc(); + + if (InnerLoc.isInvalid() || InnerLoc.isMacroID() || OuterLoc.isInvalid() || + OuterLoc.isMacroID()) + continue; + + if (SM.getExpansionLineNumber(InnerLoc) == + SM.getExpansionLineNumber(OuterLoc)) + continue; + + const Stmt *NextStmt = CStmt->body_begin()[I + 1]; + SourceLocation NextLoc = NextStmt->getBeginLoc(); + + if (NextLoc.isInvalid() || NextLoc.isMacroID()) + continue; + + if (SM.getExpansionColumnNumber(InnerLoc) == + SM.getExpansionColumnNumber(NextLoc)) { + diag(NextLoc, "misleading indentation: statement is indented too deeply"); + diag(OuterLoc, "did you mean this line to be inside this '%0'", + DiagnosticIDs::Note) + << StmtNames[StmtKind]; + } + } +} + +void MisleadingIndentationCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(ifStmt(hasElse(stmt())).bind("if"), this); + Finder->addMatcher( + compoundStmt(has(stmt(anyOf(ifStmt(), forStmt(), whileStmt())))) + .bind("compound"), + this); +} + +void MisleadingIndentationCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *If = Result.Nodes.getNodeAs<IfStmt>("if")) + danglingElseCheck(*Result.SourceManager, Result.Context, If); + + if (const auto *CStmt = Result.Nodes.getNodeAs<CompoundStmt>("compound")) + missingBracesCheck(*Result.SourceManager, CStmt); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/MisleadingIndentationCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MisleadingIndentationCheck.h new file mode 100644 index 0000000000..c336abbc7c --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MisleadingIndentationCheck.h @@ -0,0 +1,40 @@ +//===--- MisleadingIndentationCheck.h - clang-tidy---------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISLEADING_INDENTATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISLEADING_INDENTATION_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Checks the code for dangling else, and possible misleading indentations due +/// to missing braces. Note that this check only works as expected when the tabs +/// or spaces are used consistently and not mixed. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/misleading-indentation.html +class MisleadingIndentationCheck : public ClangTidyCheck { +public: + MisleadingIndentationCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + void danglingElseCheck(const SourceManager &SM, ASTContext *Context, + const IfStmt *If); + void missingBracesCheck(const SourceManager &SM, const CompoundStmt *CStmt); +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISLEADING_INDENTATION_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/MisplacedArrayIndexCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MisplacedArrayIndexCheck.cpp new file mode 100644 index 0000000000..328d1896ce --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MisplacedArrayIndexCheck.cpp @@ -0,0 +1,53 @@ +//===--- MisplacedArrayIndexCheck.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 "MisplacedArrayIndexCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +void MisplacedArrayIndexCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + traverse(TK_AsIs, arraySubscriptExpr(hasLHS(hasType(isInteger())), + hasRHS(hasType(isAnyPointer()))) + .bind("expr")), + this); +} + +void MisplacedArrayIndexCheck::check(const MatchFinder::MatchResult &Result) { + const auto *ArraySubscriptE = + Result.Nodes.getNodeAs<ArraySubscriptExpr>("expr"); + + auto Diag = diag(ArraySubscriptE->getBeginLoc(), "confusing array subscript " + "expression, usually the " + "index is inside the []"); + + // Only try to fixit when LHS and RHS can be swapped directly without changing + // the logic. + const Expr *RHSE = ArraySubscriptE->getRHS()->IgnoreParenImpCasts(); + if (!isa<StringLiteral>(RHSE) && !isa<DeclRefExpr>(RHSE) && + !isa<MemberExpr>(RHSE)) + return; + + const StringRef LText = tooling::fixit::getText( + ArraySubscriptE->getLHS()->getSourceRange(), *Result.Context); + const StringRef RText = tooling::fixit::getText( + ArraySubscriptE->getRHS()->getSourceRange(), *Result.Context); + + Diag << FixItHint::CreateReplacement( + ArraySubscriptE->getLHS()->getSourceRange(), RText); + Diag << FixItHint::CreateReplacement( + ArraySubscriptE->getRHS()->getSourceRange(), LText); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/MisplacedArrayIndexCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MisplacedArrayIndexCheck.h new file mode 100644 index 0000000000..1ccd011b30 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MisplacedArrayIndexCheck.h @@ -0,0 +1,31 @@ +//===--- MisplacedArrayIndexCheck.h - clang-tidy-----------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISPLACED_ARRAY_INDEX_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISPLACED_ARRAY_INDEX_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Warn about unusual array index syntax (`index[array]` instead of +/// `array[index]`). +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/misplaced-array-index.html +class MisplacedArrayIndexCheck : public ClangTidyCheck { +public: + MisplacedArrayIndexCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_MISPLACED_ARRAY_INDEX_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/NamedParameterCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/NamedParameterCheck.cpp new file mode 100644 index 0000000000..f621cfe706 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/NamedParameterCheck.cpp @@ -0,0 +1,118 @@ +//===--- NamedParameterCheck.cpp - clang-tidy -------------------*- C++ -*-===// +// +// 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 "NamedParameterCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +void NamedParameterCheck::registerMatchers(ast_matchers::MatchFinder *Finder) { + Finder->addMatcher(functionDecl().bind("decl"), this); +} + +void NamedParameterCheck::check(const MatchFinder::MatchResult &Result) { + const SourceManager &SM = *Result.SourceManager; + const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("decl"); + SmallVector<std::pair<const FunctionDecl *, unsigned>, 4> UnnamedParams; + + // Ignore declarations without a definition if we're not dealing with an + // overriden method. + const FunctionDecl *Definition = nullptr; + if ((!Function->isDefined(Definition) || Function->isDefaulted() || + Function->isDeleted()) && + (!isa<CXXMethodDecl>(Function) || + cast<CXXMethodDecl>(Function)->size_overridden_methods() == 0)) + return; + + // TODO: Handle overloads. + // TODO: We could check that all redeclarations use the same name for + // arguments in the same position. + for (unsigned I = 0, E = Function->getNumParams(); I != E; ++I) { + const ParmVarDecl *Parm = Function->getParamDecl(I); + if (Parm->isImplicit()) + continue; + // Look for unnamed parameters. + if (!Parm->getName().empty()) + continue; + + // Don't warn on the dummy argument on post-inc and post-dec operators. + if ((Function->getOverloadedOperator() == OO_PlusPlus || + Function->getOverloadedOperator() == OO_MinusMinus) && + Parm->getType()->isSpecificBuiltinType(BuiltinType::Int)) + continue; + + // Sanity check the source locations. + if (!Parm->getLocation().isValid() || Parm->getLocation().isMacroID() || + !SM.isWrittenInSameFile(Parm->getBeginLoc(), Parm->getLocation())) + continue; + + // Skip gmock testing::Unused parameters. + if (const auto *Typedef = Parm->getType()->getAs<clang::TypedefType>()) + if (Typedef->getDecl()->getQualifiedNameAsString() == "testing::Unused") + continue; + + // Skip std::nullptr_t. + if (Parm->getType().getCanonicalType()->isNullPtrType()) + continue; + + // Look for comments. We explicitly want to allow idioms like + // void foo(int /*unused*/) + const char *Begin = SM.getCharacterData(Parm->getBeginLoc()); + const char *End = SM.getCharacterData(Parm->getLocation()); + StringRef Data(Begin, End - Begin); + if (Data.contains("/*")) + continue; + + UnnamedParams.push_back(std::make_pair(Function, I)); + } + + // Emit only one warning per function but fixits for all unnamed parameters. + if (!UnnamedParams.empty()) { + const ParmVarDecl *FirstParm = + UnnamedParams.front().first->getParamDecl(UnnamedParams.front().second); + auto D = diag(FirstParm->getLocation(), + "all parameters should be named in a function"); + + for (auto P : UnnamedParams) { + // Fallback to an unused marker. + StringRef NewName = "unused"; + + // If the method is overridden, try to copy the name from the base method + // into the overrider. + const auto *M = dyn_cast<CXXMethodDecl>(P.first); + if (M && M->size_overridden_methods() > 0) { + const ParmVarDecl *OtherParm = + (*M->begin_overridden_methods())->getParamDecl(P.second); + StringRef Name = OtherParm->getName(); + if (!Name.empty()) + NewName = Name; + } + + // If the definition has a named parameter use that name. + if (Definition) { + const ParmVarDecl *DefParm = Definition->getParamDecl(P.second); + StringRef Name = DefParm->getName(); + if (!Name.empty()) + NewName = Name; + } + + // Now insert the comment. Note that getLocation() points to the place + // where the name would be, this allows us to also get complex cases like + // function pointers right. + const ParmVarDecl *Parm = P.first->getParamDecl(P.second); + D << FixItHint::CreateInsertion(Parm->getLocation(), + " /*" + NewName.str() + "*/"); + } + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/NamedParameterCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/NamedParameterCheck.h new file mode 100644 index 0000000000..812d90ef73 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/NamedParameterCheck.h @@ -0,0 +1,40 @@ +//===--- NamedParameterCheck.h - clang-tidy ---------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMEDPARAMETERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMEDPARAMETERCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Find functions with unnamed arguments. +/// +/// The check implements the following rule originating in the Google C++ Style +/// Guide: +/// +/// https://google.github.io/styleguide/cppguide.html#Function_Declarations_and_Definitions +/// +/// All parameters should be named, with identical names in the declaration and +/// implementation. +/// +/// Corresponding cpplint.py check name: 'readability/function'. +class NamedParameterCheck : public ClangTidyCheck { +public: + NamedParameterCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMEDPARAMETERCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/NamespaceCommentCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/NamespaceCommentCheck.cpp new file mode 100644 index 0000000000..ad2d396311 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/NamespaceCommentCheck.cpp @@ -0,0 +1,205 @@ +//===--- NamespaceCommentCheck.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 "NamespaceCommentCheck.h" +#include "../utils/LexerUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/TokenKinds.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringExtras.h" +#include <optional> + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +NamespaceCommentCheck::NamespaceCommentCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + NamespaceCommentPattern("^/[/*] *(end (of )?)? *(anonymous|unnamed)? *" + "namespace( +([a-zA-Z0-9_:]+))?\\.? *(\\*/)?$", + llvm::Regex::IgnoreCase), + ShortNamespaceLines(Options.get("ShortNamespaceLines", 1u)), + SpacesBeforeComments(Options.get("SpacesBeforeComments", 1u)) {} + +void NamespaceCommentCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ShortNamespaceLines", ShortNamespaceLines); + Options.store(Opts, "SpacesBeforeComments", SpacesBeforeComments); +} + +void NamespaceCommentCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(namespaceDecl().bind("namespace"), this); +} + +static bool locationsInSameFile(const SourceManager &Sources, + SourceLocation Loc1, SourceLocation Loc2) { + return Loc1.isFileID() && Loc2.isFileID() && + Sources.getFileID(Loc1) == Sources.getFileID(Loc2); +} + +static std::optional<std::string> +getNamespaceNameAsWritten(SourceLocation &Loc, const SourceManager &Sources, + const LangOptions &LangOpts) { + // Loc should be at the begin of the namespace decl (usually, `namespace` + // token). We skip the first token right away, but in case of `inline + // namespace` or `namespace a::inline b` we can see both `inline` and + // `namespace` keywords, which we just ignore. Nested parens/squares before + // the opening brace can result from attributes. + std::string Result; + int Nesting = 0; + while (std::optional<Token> T = utils::lexer::findNextTokenSkippingComments( + Loc, Sources, LangOpts)) { + Loc = T->getLocation(); + if (T->is(tok::l_brace)) + break; + + if (T->isOneOf(tok::l_square, tok::l_paren)) { + ++Nesting; + } else if (T->isOneOf(tok::r_square, tok::r_paren)) { + --Nesting; + } else if (Nesting == 0) { + if (T->is(tok::raw_identifier)) { + StringRef ID = T->getRawIdentifier(); + if (ID != "namespace" && ID != "inline") + Result.append(std::string(ID)); + } else if (T->is(tok::coloncolon)) { + Result.append("::"); + } else { // Any other kind of token is unexpected here. + return std::nullopt; + } + } + } + return Result; +} + +void NamespaceCommentCheck::check(const MatchFinder::MatchResult &Result) { + const auto *ND = Result.Nodes.getNodeAs<NamespaceDecl>("namespace"); + const SourceManager &Sources = *Result.SourceManager; + + // Ignore namespaces inside macros and namespaces split across files. + if (ND->getBeginLoc().isMacroID() || + !locationsInSameFile(Sources, ND->getBeginLoc(), ND->getRBraceLoc())) + return; + + // Don't require closing comments for namespaces spanning less than certain + // number of lines. + unsigned StartLine = Sources.getSpellingLineNumber(ND->getBeginLoc()); + unsigned EndLine = Sources.getSpellingLineNumber(ND->getRBraceLoc()); + if (EndLine - StartLine + 1 <= ShortNamespaceLines) + return; + + // Find next token after the namespace closing brace. + SourceLocation AfterRBrace = Lexer::getLocForEndOfToken( + ND->getRBraceLoc(), /*Offset=*/0, Sources, getLangOpts()); + SourceLocation Loc = AfterRBrace; + SourceLocation LBraceLoc = ND->getBeginLoc(); + + // Currently for nested namespace (n1::n2::...) the AST matcher will match foo + // then bar instead of a single match. So if we got a nested namespace we have + // to skip the next ones. + for (const auto &EndOfNameLocation : Ends) { + if (Sources.isBeforeInTranslationUnit(ND->getLocation(), EndOfNameLocation)) + return; + } + + std::optional<std::string> NamespaceNameAsWritten = + getNamespaceNameAsWritten(LBraceLoc, Sources, getLangOpts()); + if (!NamespaceNameAsWritten) + return; + + if (NamespaceNameAsWritten->empty() != ND->isAnonymousNamespace()) { + // Apparently, we didn't find the correct namespace name. Give up. + return; + } + + Ends.push_back(LBraceLoc); + + Token Tok; + // Skip whitespace until we find the next token. + while (Lexer::getRawToken(Loc, Tok, Sources, getLangOpts()) || + Tok.is(tok::semi)) { + Loc = Loc.getLocWithOffset(1); + } + + if (!locationsInSameFile(Sources, ND->getRBraceLoc(), Loc)) + return; + + bool NextTokenIsOnSameLine = Sources.getSpellingLineNumber(Loc) == EndLine; + // If we insert a line comment before the token in the same line, we need + // to insert a line break. + bool NeedLineBreak = NextTokenIsOnSameLine && Tok.isNot(tok::eof); + + SourceRange OldCommentRange(AfterRBrace, AfterRBrace); + std::string Message = "%0 not terminated with a closing comment"; + + // Try to find existing namespace closing comment on the same line. + if (Tok.is(tok::comment) && NextTokenIsOnSameLine) { + StringRef Comment(Sources.getCharacterData(Loc), Tok.getLength()); + SmallVector<StringRef, 7> Groups; + if (NamespaceCommentPattern.match(Comment, &Groups)) { + StringRef NamespaceNameInComment = Groups.size() > 5 ? Groups[5] : ""; + StringRef Anonymous = Groups.size() > 3 ? Groups[3] : ""; + + if ((ND->isAnonymousNamespace() && NamespaceNameInComment.empty()) || + (*NamespaceNameAsWritten == NamespaceNameInComment && + Anonymous.empty())) { + // Check if the namespace in the comment is the same. + // FIXME: Maybe we need a strict mode, where we always fix namespace + // comments with different format. + return; + } + + // Otherwise we need to fix the comment. + NeedLineBreak = Comment.startswith("/*"); + OldCommentRange = + SourceRange(AfterRBrace, Loc.getLocWithOffset(Tok.getLength())); + Message = + (llvm::Twine( + "%0 ends with a comment that refers to a wrong namespace '") + + NamespaceNameInComment + "'") + .str(); + } else if (Comment.startswith("//")) { + // Assume that this is an unrecognized form of a namespace closing line + // comment. Replace it. + NeedLineBreak = false; + OldCommentRange = + SourceRange(AfterRBrace, Loc.getLocWithOffset(Tok.getLength())); + Message = "%0 ends with an unrecognized comment"; + } + // If it's a block comment, just move it to the next line, as it can be + // multi-line or there may be other tokens behind it. + } + + std::string NamespaceNameForDiag = + ND->isAnonymousNamespace() ? "anonymous namespace" + : ("namespace '" + *NamespaceNameAsWritten + "'"); + + std::string Fix(SpacesBeforeComments, ' '); + Fix.append("// namespace"); + if (!ND->isAnonymousNamespace()) + Fix.append(" ").append(*NamespaceNameAsWritten); + if (NeedLineBreak) + Fix.append("\n"); + + // Place diagnostic at an old comment, or closing brace if we did not have it. + SourceLocation DiagLoc = + OldCommentRange.getBegin() != OldCommentRange.getEnd() + ? OldCommentRange.getBegin() + : ND->getRBraceLoc(); + + diag(DiagLoc, Message) << NamespaceNameForDiag + << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(OldCommentRange), + Fix); + diag(ND->getLocation(), "%0 starts here", DiagnosticIDs::Note) + << NamespaceNameForDiag; +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/NamespaceCommentCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/NamespaceCommentCheck.h new file mode 100644 index 0000000000..fc73d3e06f --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/NamespaceCommentCheck.h @@ -0,0 +1,42 @@ +//===--- NamespaceCommentCheck.h - clang-tidy -------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMESPACECOMMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMESPACECOMMENTCHECK_H + +#include "../ClangTidyCheck.h" +#include "llvm/Support/Regex.h" + +namespace clang::tidy::readability { + +/// Checks that long namespaces have a closing comment. +/// +/// http://llvm.org/docs/CodingStandards.html#namespace-indentation +/// +/// https://google.github.io/styleguide/cppguide.html#Namespaces +class NamespaceCommentCheck : public ClangTidyCheck { +public: + NamespaceCommentCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void storeOptions(ClangTidyOptions::OptionMap &Options) override; + + llvm::Regex NamespaceCommentPattern; + const unsigned ShortNamespaceLines; + const unsigned SpacesBeforeComments; + llvm::SmallVector<SourceLocation, 4> Ends; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NAMESPACECOMMENTCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/NonConstParameterCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/NonConstParameterCheck.cpp new file mode 100644 index 0000000000..974909f3ab --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/NonConstParameterCheck.cpp @@ -0,0 +1,234 @@ +//===--- NonConstParameterCheck.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 "NonConstParameterCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +void NonConstParameterCheck::registerMatchers(MatchFinder *Finder) { + // Add parameters to Parameters. + Finder->addMatcher(parmVarDecl().bind("Parm"), this); + + // C++ constructor. + Finder->addMatcher(cxxConstructorDecl().bind("Ctor"), this); + + // Track unused parameters, there is Wunused-parameter about unused + // parameters. + Finder->addMatcher(declRefExpr().bind("Ref"), this); + + // Analyse parameter usage in function. + Finder->addMatcher(stmt(anyOf(unaryOperator(hasAnyOperatorName("++", "--")), + binaryOperator(), callExpr(), returnStmt(), + cxxConstructExpr())) + .bind("Mark"), + this); + Finder->addMatcher(varDecl(hasInitializer(anything())).bind("Mark"), this); +} + +void NonConstParameterCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Parm = Result.Nodes.getNodeAs<ParmVarDecl>("Parm")) { + if (const DeclContext *D = Parm->getParentFunctionOrMethod()) { + if (const auto *M = dyn_cast<CXXMethodDecl>(D)) { + if (M->isVirtual() || M->size_overridden_methods() != 0) + return; + } + } + addParm(Parm); + } else if (const auto *Ctor = + Result.Nodes.getNodeAs<CXXConstructorDecl>("Ctor")) { + for (const auto *Parm : Ctor->parameters()) + addParm(Parm); + for (const auto *Init : Ctor->inits()) + markCanNotBeConst(Init->getInit(), true); + } else if (const auto *Ref = Result.Nodes.getNodeAs<DeclRefExpr>("Ref")) { + setReferenced(Ref); + } else if (const auto *S = Result.Nodes.getNodeAs<Stmt>("Mark")) { + if (const auto *B = dyn_cast<BinaryOperator>(S)) { + if (B->isAssignmentOp()) + markCanNotBeConst(B, false); + } else if (const auto *CE = dyn_cast<CallExpr>(S)) { + // Typically, if a parameter is const then it is fine to make the data + // const. But sometimes the data is written even though the parameter + // is const. Mark all data passed by address to the function. + for (const auto *Arg : CE->arguments()) { + markCanNotBeConst(Arg->IgnoreParenCasts(), true); + } + + // Data passed by nonconst reference should not be made const. + if (const FunctionDecl *FD = CE->getDirectCallee()) { + unsigned ArgNr = 0U; + for (const auto *Par : FD->parameters()) { + if (ArgNr >= CE->getNumArgs()) + break; + const Expr *Arg = CE->getArg(ArgNr++); + // Is this a non constant reference parameter? + const Type *ParType = Par->getType().getTypePtr(); + if (!ParType->isReferenceType() || Par->getType().isConstQualified()) + continue; + markCanNotBeConst(Arg->IgnoreParenCasts(), false); + } + } + } else if (const auto *CE = dyn_cast<CXXConstructExpr>(S)) { + for (const auto *Arg : CE->arguments()) { + markCanNotBeConst(Arg->IgnoreParenCasts(), true); + } + // Data passed by nonconst reference should not be made const. + unsigned ArgNr = 0U; + if (const auto *CD = CE->getConstructor()) { + for (const auto *Par : CD->parameters()) { + if (ArgNr >= CE->getNumArgs()) + break; + const Expr *Arg = CE->getArg(ArgNr++); + // Is this a non constant reference parameter? + const Type *ParType = Par->getType().getTypePtr(); + if (!ParType->isReferenceType() || Par->getType().isConstQualified()) + continue; + markCanNotBeConst(Arg->IgnoreParenCasts(), false); + } + } + } else if (const auto *R = dyn_cast<ReturnStmt>(S)) { + markCanNotBeConst(R->getRetValue(), true); + } else if (const auto *U = dyn_cast<UnaryOperator>(S)) { + markCanNotBeConst(U, true); + } + } else if (const auto *VD = Result.Nodes.getNodeAs<VarDecl>("Mark")) { + const QualType T = VD->getType(); + if ((T->isPointerType() && !T->getPointeeType().isConstQualified()) || + T->isArrayType()) + markCanNotBeConst(VD->getInit(), true); + else if (T->isLValueReferenceType() && + !T->getPointeeType().isConstQualified()) + markCanNotBeConst(VD->getInit(), false); + } +} + +void NonConstParameterCheck::addParm(const ParmVarDecl *Parm) { + // Only add nonconst integer/float pointer parameters. + const QualType T = Parm->getType(); + if (!T->isPointerType() || T->getPointeeType().isConstQualified() || + !(T->getPointeeType()->isIntegerType() || + T->getPointeeType()->isFloatingType())) + return; + + if (Parameters.find(Parm) != Parameters.end()) + return; + + ParmInfo PI; + PI.IsReferenced = false; + PI.CanBeConst = true; + Parameters[Parm] = PI; +} + +void NonConstParameterCheck::setReferenced(const DeclRefExpr *Ref) { + auto It = Parameters.find(dyn_cast<ParmVarDecl>(Ref->getDecl())); + if (It != Parameters.end()) + It->second.IsReferenced = true; +} + +void NonConstParameterCheck::onEndOfTranslationUnit() { + diagnoseNonConstParameters(); +} + +void NonConstParameterCheck::diagnoseNonConstParameters() { + for (const auto &It : Parameters) { + const ParmVarDecl *Par = It.first; + const ParmInfo &ParamInfo = It.second; + + // Unused parameter => there are other warnings about this. + if (!ParamInfo.IsReferenced) + continue; + + // Parameter can't be const. + if (!ParamInfo.CanBeConst) + continue; + + SmallVector<FixItHint, 8> Fixes; + auto *Function = + dyn_cast_or_null<const FunctionDecl>(Par->getParentFunctionOrMethod()); + if (!Function) + continue; + unsigned Index = Par->getFunctionScopeIndex(); + for (FunctionDecl *FnDecl : Function->redecls()) + Fixes.push_back(FixItHint::CreateInsertion( + FnDecl->getParamDecl(Index)->getBeginLoc(), "const ")); + + diag(Par->getLocation(), "pointer parameter '%0' can be pointer to const") + << Par->getName() << Fixes; + } +} + +void NonConstParameterCheck::markCanNotBeConst(const Expr *E, + bool CanNotBeConst) { + if (!E) + return; + + if (const auto *Cast = dyn_cast<ImplicitCastExpr>(E)) { + // If expression is const then ignore usage. + const QualType T = Cast->getType(); + if (T->isPointerType() && T->getPointeeType().isConstQualified()) + return; + } + + E = E->IgnoreParenCasts(); + + if (const auto *B = dyn_cast<BinaryOperator>(E)) { + if (B->isAdditiveOp()) { + // p + 2 + markCanNotBeConst(B->getLHS(), CanNotBeConst); + markCanNotBeConst(B->getRHS(), CanNotBeConst); + } else if (B->isAssignmentOp()) { + markCanNotBeConst(B->getLHS(), false); + + // If LHS is not const then RHS can't be const. + const QualType T = B->getLHS()->getType(); + if (T->isPointerType() && !T->getPointeeType().isConstQualified()) + markCanNotBeConst(B->getRHS(), true); + } + } else if (const auto *C = dyn_cast<ConditionalOperator>(E)) { + markCanNotBeConst(C->getTrueExpr(), CanNotBeConst); + markCanNotBeConst(C->getFalseExpr(), CanNotBeConst); + } else if (const auto *U = dyn_cast<UnaryOperator>(E)) { + if (U->getOpcode() == UO_PreInc || U->getOpcode() == UO_PreDec || + U->getOpcode() == UO_PostInc || U->getOpcode() == UO_PostDec) { + if (const auto *SubU = + dyn_cast<UnaryOperator>(U->getSubExpr()->IgnoreParenCasts())) + markCanNotBeConst(SubU->getSubExpr(), true); + markCanNotBeConst(U->getSubExpr(), CanNotBeConst); + } else if (U->getOpcode() == UO_Deref) { + if (!CanNotBeConst) + markCanNotBeConst(U->getSubExpr(), true); + } else { + markCanNotBeConst(U->getSubExpr(), CanNotBeConst); + } + } else if (const auto *A = dyn_cast<ArraySubscriptExpr>(E)) { + markCanNotBeConst(A->getBase(), true); + } else if (const auto *CLE = dyn_cast<CompoundLiteralExpr>(E)) { + markCanNotBeConst(CLE->getInitializer(), true); + } else if (const auto *Constr = dyn_cast<CXXConstructExpr>(E)) { + for (const auto *Arg : Constr->arguments()) { + if (const auto *M = dyn_cast<MaterializeTemporaryExpr>(Arg)) + markCanNotBeConst(cast<Expr>(M->getSubExpr()), CanNotBeConst); + } + } else if (const auto *ILE = dyn_cast<InitListExpr>(E)) { + for (unsigned I = 0U; I < ILE->getNumInits(); ++I) + markCanNotBeConst(ILE->getInit(I), true); + } else if (CanNotBeConst) { + // Referencing parameter. + if (const auto *D = dyn_cast<DeclRefExpr>(E)) { + auto It = Parameters.find(dyn_cast<ParmVarDecl>(D->getDecl())); + if (It != Parameters.end()) + It->second.CanBeConst = false; + } + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/NonConstParameterCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/NonConstParameterCheck.h new file mode 100644 index 0000000000..e2598dd01d --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/NonConstParameterCheck.h @@ -0,0 +1,62 @@ +//===--- NonConstParameterCheck.h - clang-tidy-------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NON_CONST_PARAMETER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NON_CONST_PARAMETER_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Warn when a pointer function parameter can be const. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/non-const-parameter.html +class NonConstParameterCheck : public ClangTidyCheck { +public: + NonConstParameterCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void onEndOfTranslationUnit() override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + /// Parameter info. + struct ParmInfo { + /// Is function parameter referenced? + bool IsReferenced; + + /// Can function parameter be const? + bool CanBeConst; + }; + + /// Track all nonconst integer/float parameters. + std::map<const ParmVarDecl *, ParmInfo> Parameters; + + /// Add function parameter. + void addParm(const ParmVarDecl *Parm); + + /// Set IsReferenced. + void setReferenced(const DeclRefExpr *Ref); + + /// Set CanNotBeConst. + /// Visits sub expressions recursively. If a DeclRefExpr is found + /// and CanNotBeConst is true the Parameter is marked as not-const. + /// The CanNotBeConst is updated as sub expressions are visited. + void markCanNotBeConst(const Expr *E, bool CanNotBeConst); + + /// Diagnose non const parameters. + void diagnoseNonConstParameters(); +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_NON_CONST_PARAMETER_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/QualifiedAutoCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/QualifiedAutoCheck.cpp new file mode 100644 index 0000000000..e843c593a9 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/QualifiedAutoCheck.cpp @@ -0,0 +1,287 @@ +//===--- QualifiedAutoCheck.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 "QualifiedAutoCheck.h" +#include "../utils/LexerUtils.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "llvm/ADT/SmallVector.h" +#include <optional> + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +namespace { + +// FIXME move to ASTMatchers +AST_MATCHER_P(QualType, hasUnqualifiedType, + ast_matchers::internal::Matcher<QualType>, InnerMatcher) { + return InnerMatcher.matches(Node.getUnqualifiedType(), Finder, Builder); +} + +enum class Qualifier { Const, Volatile, Restrict }; + +std::optional<Token> findQualToken(const VarDecl *Decl, Qualifier Qual, + const MatchFinder::MatchResult &Result) { + // Since either of the locs can be in a macro, use `makeFileCharRange` to be + // sure that we have a consistent `CharSourceRange`, located entirely in the + // source file. + + assert((Qual == Qualifier::Const || Qual == Qualifier::Volatile || + Qual == Qualifier::Restrict) && + "Invalid Qualifier"); + + SourceLocation BeginLoc = Decl->getQualifierLoc().getBeginLoc(); + if (BeginLoc.isInvalid()) + BeginLoc = Decl->getBeginLoc(); + SourceLocation EndLoc = Decl->getLocation(); + + CharSourceRange FileRange = Lexer::makeFileCharRange( + CharSourceRange::getCharRange(BeginLoc, EndLoc), *Result.SourceManager, + Result.Context->getLangOpts()); + + if (FileRange.isInvalid()) + return std::nullopt; + + tok::TokenKind Tok = + Qual == Qualifier::Const + ? tok::kw_const + : Qual == Qualifier::Volatile ? tok::kw_volatile : tok::kw_restrict; + + return utils::lexer::getQualifyingToken(Tok, FileRange, *Result.Context, + *Result.SourceManager); +} + +std::optional<SourceRange> +getTypeSpecifierLocation(const VarDecl *Var, + const MatchFinder::MatchResult &Result) { + SourceRange TypeSpecifier( + Var->getTypeSpecStartLoc(), + Var->getTypeSpecEndLoc().getLocWithOffset(Lexer::MeasureTokenLength( + Var->getTypeSpecEndLoc(), *Result.SourceManager, + Result.Context->getLangOpts()))); + + if (TypeSpecifier.getBegin().isMacroID() || + TypeSpecifier.getEnd().isMacroID()) + return std::nullopt; + return TypeSpecifier; +} + +std::optional<SourceRange> mergeReplacementRange(SourceRange &TypeSpecifier, + const Token &ConstToken) { + if (TypeSpecifier.getBegin().getLocWithOffset(-1) == ConstToken.getEndLoc()) { + TypeSpecifier.setBegin(ConstToken.getLocation()); + return std::nullopt; + } + if (TypeSpecifier.getEnd().getLocWithOffset(1) == ConstToken.getLocation()) { + TypeSpecifier.setEnd(ConstToken.getEndLoc()); + return std::nullopt; + } + return SourceRange(ConstToken.getLocation(), ConstToken.getEndLoc()); +} + +bool isPointerConst(QualType QType) { + QualType Pointee = QType->getPointeeType(); + assert(!Pointee.isNull() && "can't have a null Pointee"); + return Pointee.isConstQualified(); +} + +bool isAutoPointerConst(QualType QType) { + QualType Pointee = + cast<AutoType>(QType->getPointeeType().getTypePtr())->desugar(); + assert(!Pointee.isNull() && "can't have a null Pointee"); + return Pointee.isConstQualified(); +} + +} // namespace + +void QualifiedAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "AddConstToQualified", AddConstToQualified); +} + +void QualifiedAutoCheck::registerMatchers(MatchFinder *Finder) { + auto ExplicitSingleVarDecl = + [](const ast_matchers::internal::Matcher<VarDecl> &InnerMatcher, + llvm::StringRef ID) { + return declStmt( + unless(isInTemplateInstantiation()), + hasSingleDecl( + varDecl(unless(isImplicit()), InnerMatcher).bind(ID))); + }; + auto ExplicitSingleVarDeclInTemplate = + [](const ast_matchers::internal::Matcher<VarDecl> &InnerMatcher, + llvm::StringRef ID) { + return declStmt( + isInTemplateInstantiation(), + hasSingleDecl( + varDecl(unless(isImplicit()), InnerMatcher).bind(ID))); + }; + + auto IsBoundToType = refersToType(equalsBoundNode("type")); + auto UnlessFunctionType = unless(hasUnqualifiedDesugaredType(functionType())); + auto IsAutoDeducedToPointer = [](const auto &...InnerMatchers) { + return autoType(hasDeducedType( + hasUnqualifiedDesugaredType(pointerType(pointee(InnerMatchers...))))); + }; + + Finder->addMatcher( + ExplicitSingleVarDecl(hasType(IsAutoDeducedToPointer(UnlessFunctionType)), + "auto"), + this); + + Finder->addMatcher( + ExplicitSingleVarDeclInTemplate( + allOf(hasType(IsAutoDeducedToPointer( + hasUnqualifiedType(qualType().bind("type")), + UnlessFunctionType)), + anyOf(hasAncestor( + functionDecl(hasAnyTemplateArgument(IsBoundToType))), + hasAncestor(classTemplateSpecializationDecl( + hasAnyTemplateArgument(IsBoundToType))))), + "auto"), + this); + if (!AddConstToQualified) + return; + Finder->addMatcher(ExplicitSingleVarDecl( + hasType(pointerType(pointee(autoType()))), "auto_ptr"), + this); + Finder->addMatcher( + ExplicitSingleVarDecl(hasType(lValueReferenceType(pointee(autoType()))), + "auto_ref"), + this); +} + +void QualifiedAutoCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto")) { + SourceRange TypeSpecifier; + if (std::optional<SourceRange> TypeSpec = + getTypeSpecifierLocation(Var, Result)) { + TypeSpecifier = *TypeSpec; + } else + return; + + llvm::SmallVector<SourceRange, 4> RemoveQualifiersRange; + auto CheckQualifier = [&](bool IsPresent, Qualifier Qual) { + if (IsPresent) { + std::optional<Token> Token = findQualToken(Var, Qual, Result); + if (!Token || Token->getLocation().isMacroID()) + return true; // Disregard this VarDecl. + if (std::optional<SourceRange> Result = + mergeReplacementRange(TypeSpecifier, *Token)) + RemoveQualifiersRange.push_back(*Result); + } + return false; + }; + + bool IsLocalConst = Var->getType().isLocalConstQualified(); + bool IsLocalVolatile = Var->getType().isLocalVolatileQualified(); + bool IsLocalRestrict = Var->getType().isLocalRestrictQualified(); + + if (CheckQualifier(IsLocalConst, Qualifier::Const) || + CheckQualifier(IsLocalVolatile, Qualifier::Volatile) || + CheckQualifier(IsLocalRestrict, Qualifier::Restrict)) + return; + + // Check for bridging the gap between the asterisk and name. + if (Var->getLocation() == TypeSpecifier.getEnd().getLocWithOffset(1)) + TypeSpecifier.setEnd(TypeSpecifier.getEnd().getLocWithOffset(1)); + + CharSourceRange FixItRange = CharSourceRange::getCharRange(TypeSpecifier); + if (FixItRange.isInvalid()) + return; + + SourceLocation FixitLoc = FixItRange.getBegin(); + for (SourceRange &Range : RemoveQualifiersRange) { + if (Range.getBegin() < FixitLoc) + FixitLoc = Range.getBegin(); + } + + std::string ReplStr = [&] { + llvm::StringRef PtrConst = isPointerConst(Var->getType()) ? "const " : ""; + llvm::StringRef LocalConst = IsLocalConst ? "const " : ""; + llvm::StringRef LocalVol = IsLocalVolatile ? "volatile " : ""; + llvm::StringRef LocalRestrict = IsLocalRestrict ? "__restrict " : ""; + return (PtrConst + "auto *" + LocalConst + LocalVol + LocalRestrict) + .str(); + }(); + + DiagnosticBuilder Diag = + diag(FixitLoc, + "'%select{|const }0%select{|volatile }1%select{|__restrict }2auto " + "%3' can be declared as '%4%3'") + << IsLocalConst << IsLocalVolatile << IsLocalRestrict << Var->getName() + << ReplStr; + + for (SourceRange &Range : RemoveQualifiersRange) { + Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(Range)); + } + + Diag << FixItHint::CreateReplacement(FixItRange, ReplStr); + return; + } + if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ptr")) { + if (!isPointerConst(Var->getType())) + return; // Pointer isn't const, no need to add const qualifier. + if (!isAutoPointerConst(Var->getType())) + return; // Const isn't wrapped in the auto type, so must be declared + // explicitly. + + if (Var->getType().isLocalConstQualified()) { + std::optional<Token> Token = findQualToken(Var, Qualifier::Const, Result); + if (!Token || Token->getLocation().isMacroID()) + return; + } + if (Var->getType().isLocalVolatileQualified()) { + std::optional<Token> Token = + findQualToken(Var, Qualifier::Volatile, Result); + if (!Token || Token->getLocation().isMacroID()) + return; + } + if (Var->getType().isLocalRestrictQualified()) { + std::optional<Token> Token = + findQualToken(Var, Qualifier::Restrict, Result); + if (!Token || Token->getLocation().isMacroID()) + return; + } + + if (std::optional<SourceRange> TypeSpec = + getTypeSpecifierLocation(Var, Result)) { + if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() || + TypeSpec->getEnd().isMacroID()) + return; + SourceLocation InsertPos = TypeSpec->getBegin(); + diag(InsertPos, + "'auto *%select{|const }0%select{|volatile }1%2' can be declared as " + "'const auto *%select{|const }0%select{|volatile }1%2'") + << Var->getType().isLocalConstQualified() + << Var->getType().isLocalVolatileQualified() << Var->getName() + << FixItHint::CreateInsertion(InsertPos, "const "); + } + return; + } + if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ref")) { + if (!isPointerConst(Var->getType())) + return; // Pointer isn't const, no need to add const qualifier. + if (!isAutoPointerConst(Var->getType())) + // Const isn't wrapped in the auto type, so must be declared explicitly. + return; + + if (std::optional<SourceRange> TypeSpec = + getTypeSpecifierLocation(Var, Result)) { + if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() || + TypeSpec->getEnd().isMacroID()) + return; + SourceLocation InsertPos = TypeSpec->getBegin(); + diag(InsertPos, "'auto &%0' can be declared as 'const auto &%0'") + << Var->getName() << FixItHint::CreateInsertion(InsertPos, "const "); + } + return; + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/QualifiedAutoCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/QualifiedAutoCheck.h new file mode 100644 index 0000000000..dae9add48e --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/QualifiedAutoCheck.h @@ -0,0 +1,40 @@ +//===--- QualifiedAutoCheck.h - clang-tidy ----------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_QUALIFIEDAUTOCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_QUALIFIEDAUTOCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Finds variables declared as auto that could be declared as: +/// 'auto*' or 'const auto *' and reference variables declared as: +/// 'const auto &'. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/qualified-auto.html +class QualifiedAutoCheck : public ClangTidyCheck { +public: + QualifiedAutoCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + AddConstToQualified(Options.get("AddConstToQualified", true)) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus11; + } + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool AddConstToQualified; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_QUALIFIEDAUTOCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ReadabilityTidyModule.cpp new file mode 100644 index 0000000000..7aa0904242 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -0,0 +1,158 @@ +//===--- ReadabilityTidyModule.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 "../ClangTidy.h" +#include "../ClangTidyModule.h" +#include "../ClangTidyModuleRegistry.h" +#include "AvoidConstParamsInDecls.h" +#include "BracesAroundStatementsCheck.h" +#include "ConstReturnTypeCheck.h" +#include "ContainerContainsCheck.h" +#include "ContainerDataPointerCheck.h" +#include "ContainerSizeEmptyCheck.h" +#include "ConvertMemberFunctionsToStatic.h" +#include "DeleteNullPointerCheck.h" +#include "DuplicateIncludeCheck.h" +#include "ElseAfterReturnCheck.h" +#include "FunctionCognitiveComplexityCheck.h" +#include "FunctionSizeCheck.h" +#include "IdentifierLengthCheck.h" +#include "IdentifierNamingCheck.h" +#include "ImplicitBoolConversionCheck.h" +#include "InconsistentDeclarationParameterNameCheck.h" +#include "IsolateDeclarationCheck.h" +#include "MagicNumbersCheck.h" +#include "MakeMemberFunctionConstCheck.h" +#include "MisleadingIndentationCheck.h" +#include "MisplacedArrayIndexCheck.h" +#include "NamedParameterCheck.h" +#include "NonConstParameterCheck.h" +#include "QualifiedAutoCheck.h" +#include "RedundantAccessSpecifiersCheck.h" +#include "RedundantControlFlowCheck.h" +#include "RedundantDeclarationCheck.h" +#include "RedundantFunctionPtrDereferenceCheck.h" +#include "RedundantMemberInitCheck.h" +#include "RedundantPreprocessorCheck.h" +#include "RedundantSmartptrGetCheck.h" +#include "RedundantStringCStrCheck.h" +#include "RedundantStringInitCheck.h" +#include "SimplifyBooleanExprCheck.h" +#include "SimplifySubscriptExprCheck.h" +#include "StaticAccessedThroughInstanceCheck.h" +#include "StaticDefinitionInAnonymousNamespaceCheck.h" +#include "StringCompareCheck.h" +#include "SuspiciousCallArgumentCheck.h" +#include "UniqueptrDeleteReleaseCheck.h" +#include "UppercaseLiteralSuffixCheck.h" +#include "UseAnyOfAllOfCheck.h" + +namespace clang::tidy { +namespace readability { + +class ReadabilityModule : public ClangTidyModule { +public: + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck<AvoidConstParamsInDecls>( + "readability-avoid-const-params-in-decls"); + CheckFactories.registerCheck<BracesAroundStatementsCheck>( + "readability-braces-around-statements"); + CheckFactories.registerCheck<ConstReturnTypeCheck>( + "readability-const-return-type"); + CheckFactories.registerCheck<ContainerContainsCheck>( + "readability-container-contains"); + CheckFactories.registerCheck<ContainerDataPointerCheck>( + "readability-container-data-pointer"); + CheckFactories.registerCheck<ContainerSizeEmptyCheck>( + "readability-container-size-empty"); + CheckFactories.registerCheck<ConvertMemberFunctionsToStatic>( + "readability-convert-member-functions-to-static"); + CheckFactories.registerCheck<DeleteNullPointerCheck>( + "readability-delete-null-pointer"); + CheckFactories.registerCheck<DuplicateIncludeCheck>( + "readability-duplicate-include"); + CheckFactories.registerCheck<ElseAfterReturnCheck>( + "readability-else-after-return"); + CheckFactories.registerCheck<FunctionCognitiveComplexityCheck>( + "readability-function-cognitive-complexity"); + CheckFactories.registerCheck<FunctionSizeCheck>( + "readability-function-size"); + CheckFactories.registerCheck<IdentifierLengthCheck>( + "readability-identifier-length"); + CheckFactories.registerCheck<IdentifierNamingCheck>( + "readability-identifier-naming"); + CheckFactories.registerCheck<ImplicitBoolConversionCheck>( + "readability-implicit-bool-conversion"); + CheckFactories.registerCheck<InconsistentDeclarationParameterNameCheck>( + "readability-inconsistent-declaration-parameter-name"); + CheckFactories.registerCheck<IsolateDeclarationCheck>( + "readability-isolate-declaration"); + CheckFactories.registerCheck<MagicNumbersCheck>( + "readability-magic-numbers"); + CheckFactories.registerCheck<MakeMemberFunctionConstCheck>( + "readability-make-member-function-const"); + CheckFactories.registerCheck<MisleadingIndentationCheck>( + "readability-misleading-indentation"); + CheckFactories.registerCheck<MisplacedArrayIndexCheck>( + "readability-misplaced-array-index"); + CheckFactories.registerCheck<QualifiedAutoCheck>( + "readability-qualified-auto"); + CheckFactories.registerCheck<RedundantAccessSpecifiersCheck>( + "readability-redundant-access-specifiers"); + CheckFactories.registerCheck<RedundantFunctionPtrDereferenceCheck>( + "readability-redundant-function-ptr-dereference"); + CheckFactories.registerCheck<RedundantMemberInitCheck>( + "readability-redundant-member-init"); + CheckFactories.registerCheck<RedundantPreprocessorCheck>( + "readability-redundant-preprocessor"); + CheckFactories.registerCheck<SimplifySubscriptExprCheck>( + "readability-simplify-subscript-expr"); + CheckFactories.registerCheck<StaticAccessedThroughInstanceCheck>( + "readability-static-accessed-through-instance"); + CheckFactories.registerCheck<StaticDefinitionInAnonymousNamespaceCheck>( + "readability-static-definition-in-anonymous-namespace"); + CheckFactories.registerCheck<StringCompareCheck>( + "readability-string-compare"); + CheckFactories.registerCheck<readability::NamedParameterCheck>( + "readability-named-parameter"); + CheckFactories.registerCheck<NonConstParameterCheck>( + "readability-non-const-parameter"); + CheckFactories.registerCheck<RedundantControlFlowCheck>( + "readability-redundant-control-flow"); + CheckFactories.registerCheck<RedundantDeclarationCheck>( + "readability-redundant-declaration"); + CheckFactories.registerCheck<RedundantSmartptrGetCheck>( + "readability-redundant-smartptr-get"); + CheckFactories.registerCheck<RedundantStringCStrCheck>( + "readability-redundant-string-cstr"); + CheckFactories.registerCheck<RedundantStringInitCheck>( + "readability-redundant-string-init"); + CheckFactories.registerCheck<SimplifyBooleanExprCheck>( + "readability-simplify-boolean-expr"); + CheckFactories.registerCheck<SuspiciousCallArgumentCheck>( + "readability-suspicious-call-argument"); + CheckFactories.registerCheck<UniqueptrDeleteReleaseCheck>( + "readability-uniqueptr-delete-release"); + CheckFactories.registerCheck<UppercaseLiteralSuffixCheck>( + "readability-uppercase-literal-suffix"); + CheckFactories.registerCheck<UseAnyOfAllOfCheck>( + "readability-use-anyofallof"); + } +}; + +// Register the ReadabilityModule using this statically initialized variable. +static ClangTidyModuleRegistry::Add<ReadabilityModule> + X("readability-module", "Adds readability-related checks."); + +} // namespace readability + +// This anchor is used to force the linker to link in the generated object file +// and thus register the ReadabilityModule. +volatile int ReadabilityModuleAnchorSource = 0; + +} // namespace clang::tidy diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantAccessSpecifiersCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantAccessSpecifiersCheck.cpp new file mode 100644 index 0000000000..c3464b2a83 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantAccessSpecifiersCheck.cpp @@ -0,0 +1,78 @@ +//===--- RedundantAccessSpecifiersCheck.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 "RedundantAccessSpecifiersCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +void RedundantAccessSpecifiersCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + cxxRecordDecl(has(accessSpecDecl())).bind("redundant-access-specifiers"), + this); +} + +void RedundantAccessSpecifiersCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedDecl = + Result.Nodes.getNodeAs<CXXRecordDecl>("redundant-access-specifiers"); + + const AccessSpecDecl *LastASDecl = nullptr; + for (DeclContext::specific_decl_iterator<AccessSpecDecl> + AS(MatchedDecl->decls_begin()), + ASEnd(MatchedDecl->decls_end()); + AS != ASEnd; ++AS) { + const AccessSpecDecl *ASDecl = *AS; + + // Ignore macro expansions. + if (ASDecl->getLocation().isMacroID()) { + LastASDecl = ASDecl; + continue; + } + + if (LastASDecl == nullptr) { + // First declaration. + LastASDecl = ASDecl; + + if (CheckFirstDeclaration) { + AccessSpecifier DefaultSpecifier = + MatchedDecl->isClass() ? AS_private : AS_public; + if (ASDecl->getAccess() == DefaultSpecifier) { + diag(ASDecl->getLocation(), + "redundant access specifier has the same accessibility as the " + "implicit access specifier") + << FixItHint::CreateRemoval(ASDecl->getSourceRange()); + } + } + + continue; + } + + if (LastASDecl->getAccess() == ASDecl->getAccess()) { + // Ignore macro expansions. + if (LastASDecl->getLocation().isMacroID()) { + LastASDecl = ASDecl; + continue; + } + + diag(ASDecl->getLocation(), + "redundant access specifier has the same accessibility as the " + "previous access specifier") + << FixItHint::CreateRemoval(ASDecl->getSourceRange()); + diag(LastASDecl->getLocation(), "previously declared here", + DiagnosticIDs::Note); + } else { + LastASDecl = ASDecl; + } + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantAccessSpecifiersCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantAccessSpecifiersCheck.h new file mode 100644 index 0000000000..a5389d063f --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantAccessSpecifiersCheck.h @@ -0,0 +1,38 @@ +//===--- RedundantAccessSpecifiersCheck.h - clang-tidy ----------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTACCESSSPECIFIERSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTACCESSSPECIFIERSCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Detects redundant access specifiers inside classes, structs, and unions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-access-specifiers.html +class RedundantAccessSpecifiersCheck : public ClangTidyCheck { +public: + RedundantAccessSpecifiersCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + CheckFirstDeclaration( + Options.getLocalOrGlobal("CheckFirstDeclaration", false)) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool CheckFirstDeclaration; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTACCESSSPECIFIERSCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantControlFlowCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantControlFlowCheck.cpp new file mode 100644 index 0000000000..ed01d3a2d7 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantControlFlowCheck.cpp @@ -0,0 +1,92 @@ +//===--- RedundantControlFlowCheck.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 "RedundantControlFlowCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +namespace { + +const char *const RedundantReturnDiag = "redundant return statement at the end " + "of a function with a void return type"; +const char *const RedundantContinueDiag = "redundant continue statement at the " + "end of loop statement"; + +bool isLocationInMacroExpansion(const SourceManager &SM, SourceLocation Loc) { + return SM.isMacroBodyExpansion(Loc) || SM.isMacroArgExpansion(Loc); +} + +} // namespace + +void RedundantControlFlowCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + functionDecl(isDefinition(), returns(voidType()), + hasBody(compoundStmt(hasAnySubstatement( + returnStmt(unless(has(expr()))))) + .bind("return"))), + this); + Finder->addMatcher( + mapAnyOf(forStmt, cxxForRangeStmt, whileStmt, doStmt) + .with(hasBody(compoundStmt(hasAnySubstatement(continueStmt())) + .bind("continue"))), + this); +} + +void RedundantControlFlowCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Return = Result.Nodes.getNodeAs<CompoundStmt>("return")) + checkRedundantReturn(Result, Return); + else if (const auto *Continue = + Result.Nodes.getNodeAs<CompoundStmt>("continue")) + checkRedundantContinue(Result, Continue); +} + +void RedundantControlFlowCheck::checkRedundantReturn( + const MatchFinder::MatchResult &Result, const CompoundStmt *Block) { + CompoundStmt::const_reverse_body_iterator Last = Block->body_rbegin(); + if (const auto *Return = dyn_cast<ReturnStmt>(*Last)) + issueDiagnostic(Result, Block, Return->getSourceRange(), + RedundantReturnDiag); +} + +void RedundantControlFlowCheck::checkRedundantContinue( + const MatchFinder::MatchResult &Result, const CompoundStmt *Block) { + CompoundStmt::const_reverse_body_iterator Last = Block->body_rbegin(); + if (const auto *Continue = dyn_cast<ContinueStmt>(*Last)) + issueDiagnostic(Result, Block, Continue->getSourceRange(), + RedundantContinueDiag); +} + +void RedundantControlFlowCheck::issueDiagnostic( + const MatchFinder::MatchResult &Result, const CompoundStmt *const Block, + const SourceRange &StmtRange, const char *const Diag) { + SourceManager &SM = *Result.SourceManager; + if (isLocationInMacroExpansion(SM, StmtRange.getBegin())) + return; + + CompoundStmt::const_reverse_body_iterator Previous = ++Block->body_rbegin(); + SourceLocation Start; + if (Previous != Block->body_rend()) + Start = Lexer::findLocationAfterToken( + cast<Stmt>(*Previous)->getEndLoc(), tok::semi, SM, getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/true); + if (!Start.isValid()) + Start = StmtRange.getBegin(); + auto RemovedRange = CharSourceRange::getCharRange( + Start, Lexer::findLocationAfterToken( + StmtRange.getEnd(), tok::semi, SM, getLangOpts(), + /*SkipTrailingWhitespaceAndNewLine=*/true)); + + diag(StmtRange.getBegin(), Diag) << FixItHint::CreateRemoval(RemovedRange); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantControlFlowCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantControlFlowCheck.h new file mode 100644 index 0000000000..7433005bb7 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantControlFlowCheck.h @@ -0,0 +1,50 @@ +//===--- RedundantControlFlowCheck.h - clang-tidy----------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_CONTROL_FLOW_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_CONTROL_FLOW_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Eliminates redundant `return` statements at the end of a function that +/// returns `void`. +/// +/// Eliminates redundant `continue` statements at the end of a loop body. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-control-flow.html +class RedundantControlFlowCheck : public ClangTidyCheck { +public: + RedundantControlFlowCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + void + checkRedundantReturn(const ast_matchers::MatchFinder::MatchResult &Result, + const CompoundStmt *Block); + + void + checkRedundantContinue(const ast_matchers::MatchFinder::MatchResult &Result, + const CompoundStmt *Block); + + void issueDiagnostic(const ast_matchers::MatchFinder::MatchResult &Result, + const CompoundStmt *Block, const SourceRange &StmtRange, + const char *Diag); +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_CONTROL_FLOW_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantDeclarationCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantDeclarationCheck.cpp new file mode 100644 index 0000000000..f21f0a7856 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantDeclarationCheck.cpp @@ -0,0 +1,87 @@ +//===--- RedundantDeclarationCheck.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 "RedundantDeclarationCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +AST_MATCHER(FunctionDecl, doesDeclarationForceExternallyVisibleDefinition) { + return Node.doesDeclarationForceExternallyVisibleDefinition(); +} + +RedundantDeclarationCheck::RedundantDeclarationCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} + +void RedundantDeclarationCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreMacros", IgnoreMacros); +} + +void RedundantDeclarationCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + namedDecl(anyOf(varDecl(unless(isDefinition())), + functionDecl(unless(anyOf( + isDefinition(), isDefaulted(), + doesDeclarationForceExternallyVisibleDefinition(), + hasAncestor(friendDecl())))))) + .bind("Decl"), + this); +} + +void RedundantDeclarationCheck::check(const MatchFinder::MatchResult &Result) { + const auto *D = Result.Nodes.getNodeAs<NamedDecl>("Decl"); + const auto *Prev = D->getPreviousDecl(); + if (!Prev) + return; + if (!Prev->getLocation().isValid()) + return; + if (Prev->getLocation() == D->getLocation()) + return; + if (IgnoreMacros && + (D->getLocation().isMacroID() || Prev->getLocation().isMacroID())) + return; + // Don't complain when the previous declaration is a friend declaration. + for (const auto &Parent : Result.Context->getParents(*Prev)) + if (Parent.get<FriendDecl>()) + return; + + const SourceManager &SM = *Result.SourceManager; + + const bool DifferentHeaders = + !SM.isInMainFile(D->getLocation()) && + !SM.isWrittenInSameFile(Prev->getLocation(), D->getLocation()); + + bool MultiVar = false; + if (const auto *VD = dyn_cast<VarDecl>(D)) { + // Is this a multivariable declaration? + for (const auto *Other : VD->getDeclContext()->decls()) { + if (Other != D && Other->getBeginLoc() == VD->getBeginLoc()) { + MultiVar = true; + break; + } + } + } + + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + D->getSourceRange().getEnd(), 0, SM, Result.Context->getLangOpts()); + { + auto Diag = diag(D->getLocation(), "redundant %0 declaration") << D; + if (!MultiVar && !DifferentHeaders) + Diag << FixItHint::CreateRemoval( + SourceRange(D->getSourceRange().getBegin(), EndLoc)); + } + diag(Prev->getLocation(), "previously declared here", DiagnosticIDs::Note); +} +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantDeclarationCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantDeclarationCheck.h new file mode 100644 index 0000000000..a14a8aa70f --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantDeclarationCheck.h @@ -0,0 +1,33 @@ +//===--- RedundantDeclarationCheck.h - clang-tidy----------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_DECLARATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_DECLARATION_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Find redundant variable declarations. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-declaration.html +class RedundantDeclarationCheck : public ClangTidyCheck { +public: + RedundantDeclarationCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool IgnoreMacros; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_DECLARATION_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.cpp new file mode 100644 index 0000000000..247d287be2 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.cpp @@ -0,0 +1,33 @@ +//===--- RedundantFunctionPtrDereferenceCheck.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 "RedundantFunctionPtrDereferenceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +void RedundantFunctionPtrDereferenceCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + traverse(TK_AsIs, unaryOperator(hasOperatorName("*"), + has(implicitCastExpr(hasCastKind( + CK_FunctionToPointerDecay)))) + .bind("op")), + this); +} + +void RedundantFunctionPtrDereferenceCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Operator = Result.Nodes.getNodeAs<UnaryOperator>("op"); + diag(Operator->getOperatorLoc(), + "redundant repeated dereference of function pointer") + << FixItHint::CreateRemoval(Operator->getOperatorLoc()); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.h new file mode 100644 index 0000000000..fdcf821bcf --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantFunctionPtrDereferenceCheck.h @@ -0,0 +1,30 @@ +//===--- RedundantFunctionPtrDereferenceCheck.h - clang-tidy-----*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_FUNCTION_PTR_DEREFERENCE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_FUNCTION_PTR_DEREFERENCE_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Eliminate redundant dereferences of a function pointer. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-function-ptr-dereference.html +class RedundantFunctionPtrDereferenceCheck : public ClangTidyCheck { +public: + RedundantFunctionPtrDereferenceCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_FUNCTION_PTR_DEREFERENCE_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantMemberInitCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantMemberInitCheck.cpp new file mode 100644 index 0000000000..b5d407773b --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantMemberInitCheck.cpp @@ -0,0 +1,70 @@ +//===--- RedundantMemberInitCheck.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 "RedundantMemberInitCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include <algorithm> + +using namespace clang::ast_matchers; +using namespace clang::tidy::matchers; + +namespace clang::tidy::readability { + +void RedundantMemberInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreBaseInCopyConstructors", + IgnoreBaseInCopyConstructors); +} + +void RedundantMemberInitCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + cxxConstructorDecl( + unless(isDelegatingConstructor()), ofClass(unless(isUnion())), + forEachConstructorInitializer( + cxxCtorInitializer( + withInitializer( + cxxConstructExpr( + hasDeclaration( + cxxConstructorDecl(ofClass(cxxRecordDecl( + unless(isTriviallyDefaultConstructible())))))) + .bind("construct")), + unless(forField(hasType(isConstQualified()))), + unless(forField(hasParent(recordDecl(isUnion()))))) + .bind("init"))) + .bind("constructor"), + this); +} + +void RedundantMemberInitCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Init = Result.Nodes.getNodeAs<CXXCtorInitializer>("init"); + const auto *Construct = Result.Nodes.getNodeAs<CXXConstructExpr>("construct"); + const auto *ConstructorDecl = + Result.Nodes.getNodeAs<CXXConstructorDecl>("constructor"); + + if (IgnoreBaseInCopyConstructors && ConstructorDecl->isCopyConstructor() && + Init->isBaseInitializer()) + return; + + if (Construct->getNumArgs() == 0 || + Construct->getArg(0)->isDefaultArgument()) { + if (Init->isAnyMemberInitializer()) { + diag(Init->getSourceLocation(), "initializer for member %0 is redundant") + << Init->getAnyMember() + << FixItHint::CreateRemoval(Init->getSourceRange()); + } else { + diag(Init->getSourceLocation(), + "initializer for base class %0 is redundant") + << Construct->getType() + << FixItHint::CreateRemoval(Init->getSourceRange()); + } + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantMemberInitCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantMemberInitCheck.h new file mode 100644 index 0000000000..c0e0a6dac0 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantMemberInitCheck.h @@ -0,0 +1,43 @@ +//===--- RedundantMemberInitCheck.h - clang-tidy----------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_MEMBER_INIT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_MEMBER_INIT_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Finds member initializations that are unnecessary because the same default +/// constructor would be called if they were not present. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-member-init.html +class RedundantMemberInitCheck : public ClangTidyCheck { +public: + RedundantMemberInitCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreBaseInCopyConstructors( + Options.get("IgnoreBaseInCopyConstructors", false)) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + bool IgnoreBaseInCopyConstructors; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_MEMBER_INIT_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantPreprocessorCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantPreprocessorCheck.cpp new file mode 100644 index 0000000000..513687f03d --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantPreprocessorCheck.cpp @@ -0,0 +1,105 @@ +//===--- RedundantPreprocessorCheck.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 "RedundantPreprocessorCheck.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/PPCallbacks.h" +#include "clang/Lex/Preprocessor.h" + +namespace clang::tidy::readability { + +namespace { +/// Information about an opening preprocessor directive. +struct PreprocessorEntry { + SourceLocation Loc; + /// Condition used after the preprocessor directive. + std::string Condition; +}; + +const char WarningDescription[] = + "nested redundant %select{#if|#ifdef|#ifndef}0; consider removing it"; +const char NoteDescription[] = "previous %select{#if|#ifdef|#ifndef}0 was here"; + +class RedundantPreprocessorCallbacks : public PPCallbacks { + enum DirectiveKind { DK_If = 0, DK_Ifdef = 1, DK_Ifndef = 2 }; + +public: + explicit RedundantPreprocessorCallbacks(ClangTidyCheck &Check, + Preprocessor &PP) + : Check(Check), PP(PP) {} + + void If(SourceLocation Loc, SourceRange ConditionRange, + ConditionValueKind ConditionValue) override { + StringRef Condition = + Lexer::getSourceText(CharSourceRange::getTokenRange(ConditionRange), + PP.getSourceManager(), PP.getLangOpts()); + checkMacroRedundancy(Loc, Condition, IfStack, DK_If, DK_If, true); + } + + void Ifdef(SourceLocation Loc, const Token &MacroNameTok, + const MacroDefinition &MacroDefinition) override { + std::string MacroName = PP.getSpelling(MacroNameTok); + checkMacroRedundancy(Loc, MacroName, IfdefStack, DK_Ifdef, DK_Ifdef, true); + checkMacroRedundancy(Loc, MacroName, IfndefStack, DK_Ifdef, DK_Ifndef, + false); + } + + void Ifndef(SourceLocation Loc, const Token &MacroNameTok, + const MacroDefinition &MacroDefinition) override { + std::string MacroName = PP.getSpelling(MacroNameTok); + checkMacroRedundancy(Loc, MacroName, IfndefStack, DK_Ifndef, DK_Ifndef, + true); + checkMacroRedundancy(Loc, MacroName, IfdefStack, DK_Ifndef, DK_Ifdef, + false); + } + + void Endif(SourceLocation Loc, SourceLocation IfLoc) override { + if (!IfStack.empty() && IfLoc == IfStack.back().Loc) + IfStack.pop_back(); + if (!IfdefStack.empty() && IfLoc == IfdefStack.back().Loc) + IfdefStack.pop_back(); + if (!IfndefStack.empty() && IfLoc == IfndefStack.back().Loc) + IfndefStack.pop_back(); + } + +private: + void checkMacroRedundancy(SourceLocation Loc, StringRef MacroName, + SmallVector<PreprocessorEntry, 4> &Stack, + DirectiveKind WarningKind, DirectiveKind NoteKind, + bool Store) { + if (PP.getSourceManager().isInMainFile(Loc)) { + for (const auto &Entry : Stack) { + if (Entry.Condition == MacroName) { + Check.diag(Loc, WarningDescription) << WarningKind; + Check.diag(Entry.Loc, NoteDescription, DiagnosticIDs::Note) + << NoteKind; + } + } + } + + if (Store) + // This is an actual directive to be remembered. + Stack.push_back({Loc, std::string(MacroName)}); + } + + ClangTidyCheck &Check; + Preprocessor &PP; + SmallVector<PreprocessorEntry, 4> IfStack; + SmallVector<PreprocessorEntry, 4> IfdefStack; + SmallVector<PreprocessorEntry, 4> IfndefStack; +}; +} // namespace + +void RedundantPreprocessorCheck::registerPPCallbacks( + const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { + PP->addPPCallbacks( + ::std::make_unique<RedundantPreprocessorCallbacks>(*this, *PP)); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantPreprocessorCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantPreprocessorCheck.h new file mode 100644 index 0000000000..8a6fb6fd98 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantPreprocessorCheck.h @@ -0,0 +1,31 @@ +//===--- RedundantPreprocessorCheck.h - clang-tidy --------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTPREPROCESSORCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTPREPROCESSORCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// This check flags redundant preprocessor directives: nested directives with +/// the same condition. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-preprocessor.html +class RedundantPreprocessorCheck : public ClangTidyCheck { +public: + RedundantPreprocessorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTPREPROCESSORCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantSmartptrGetCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantSmartptrGetCheck.cpp new file mode 100644 index 0000000000..8837ac16e8 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantSmartptrGetCheck.cpp @@ -0,0 +1,173 @@ +//===--- RedundantSmartptrGetCheck.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 "RedundantSmartptrGetCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +namespace { +internal::Matcher<Expr> callToGet(const internal::Matcher<Decl> &OnClass) { + return expr( + anyOf(cxxMemberCallExpr( + on(expr(anyOf(hasType(OnClass), + hasType(qualType(pointsTo( + decl(OnClass).bind("ptr_to_ptr")))))) + .bind("smart_pointer")), + unless(callee( + memberExpr(hasObjectExpression(cxxThisExpr())))), + callee(cxxMethodDecl(hasName("get"), + returns(qualType(pointsTo( + type().bind("getType"))))))), + cxxDependentScopeMemberExpr( + hasMemberName("get"), + hasObjectExpression( + expr(hasType(qualType(hasCanonicalType( + templateSpecializationType(hasDeclaration( + classTemplateDecl(has(cxxRecordDecl( + OnClass, + hasMethod(cxxMethodDecl( + hasName("get"), + returns(qualType( + pointsTo(type().bind( + "getType"))))))))))))))) + .bind("smart_pointer"))))) + .bind("redundant_get"); +} + +internal::Matcher<Decl> knownSmartptr() { + return recordDecl(hasAnyName("::std::unique_ptr", "::std::shared_ptr")); +} + +void registerMatchersForGetArrowStart(MatchFinder *Finder, + MatchFinder::MatchCallback *Callback) { + const auto QuacksLikeASmartptr = recordDecl( + recordDecl().bind("duck_typing"), + has(cxxMethodDecl(hasName("operator->"), + returns(qualType(pointsTo(type().bind("op->Type")))))), + has(cxxMethodDecl(hasName("operator*"), returns(qualType(references( + type().bind("op*Type"))))))); + + // Make sure we are not missing the known standard types. + const auto Smartptr = anyOf(knownSmartptr(), QuacksLikeASmartptr); + + // Catch 'ptr.get()->Foo()' + Finder->addMatcher(memberExpr(expr().bind("memberExpr"), isArrow(), + hasObjectExpression(callToGet(Smartptr))), + Callback); + + // Catch '*ptr.get()' or '*ptr->get()' + Finder->addMatcher( + unaryOperator(hasOperatorName("*"), hasUnaryOperand(callToGet(Smartptr))), + Callback); + + // Catch '!ptr.get()' + const auto CallToGetAsBool = callToGet( + recordDecl(Smartptr, has(cxxConversionDecl(returns(booleanType()))))); + Finder->addMatcher( + unaryOperator(hasOperatorName("!"), hasUnaryOperand(CallToGetAsBool)), + Callback); + + // Catch 'if(ptr.get())' + Finder->addMatcher(ifStmt(hasCondition(CallToGetAsBool)), Callback); + + // Catch 'ptr.get() ? X : Y' + Finder->addMatcher(conditionalOperator(hasCondition(CallToGetAsBool)), + Callback); + + Finder->addMatcher(cxxDependentScopeMemberExpr(hasObjectExpression( + callExpr(has(callToGet(Smartptr))).bind("obj"))), + Callback); +} + +void registerMatchersForGetEquals(MatchFinder *Finder, + MatchFinder::MatchCallback *Callback) { + // This one is harder to do with duck typing. + // The operator==/!= that we are looking for might be member or non-member, + // might be on global namespace or found by ADL, might be a template, etc. + // For now, lets keep it to the known standard types. + + // Matches against nullptr. + Finder->addMatcher( + binaryOperator(hasAnyOperatorName("==", "!="), + hasOperands(anyOf(cxxNullPtrLiteralExpr(), gnuNullExpr(), + integerLiteral(equals(0))), + callToGet(knownSmartptr()))), + Callback); + + // FIXME: Match and fix if (l.get() == r.get()). +} + +} // namespace + +void RedundantSmartptrGetCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IgnoreMacros", IgnoreMacros); +} + +void RedundantSmartptrGetCheck::registerMatchers(MatchFinder *Finder) { + registerMatchersForGetArrowStart(Finder, this); + registerMatchersForGetEquals(Finder, this); +} + +namespace { +bool allReturnTypesMatch(const MatchFinder::MatchResult &Result) { + if (Result.Nodes.getNodeAs<Decl>("duck_typing") == nullptr) + return true; + // Verify that the types match. + // We can't do this on the matcher because the type nodes can be different, + // even though they represent the same type. This difference comes from how + // the type is referenced (eg. through a typedef, a type trait, etc). + const Type *OpArrowType = + Result.Nodes.getNodeAs<Type>("op->Type")->getUnqualifiedDesugaredType(); + const Type *OpStarType = + Result.Nodes.getNodeAs<Type>("op*Type")->getUnqualifiedDesugaredType(); + const Type *GetType = + Result.Nodes.getNodeAs<Type>("getType")->getUnqualifiedDesugaredType(); + return OpArrowType == OpStarType && OpArrowType == GetType; +} +} // namespace + +void RedundantSmartptrGetCheck::check(const MatchFinder::MatchResult &Result) { + if (!allReturnTypesMatch(Result)) + return; + + bool IsPtrToPtr = Result.Nodes.getNodeAs<Decl>("ptr_to_ptr") != nullptr; + bool IsMemberExpr = Result.Nodes.getNodeAs<Expr>("memberExpr") != nullptr; + const auto *GetCall = Result.Nodes.getNodeAs<Expr>("redundant_get"); + if (GetCall->getBeginLoc().isMacroID() && IgnoreMacros) + return; + + const auto *Smartptr = Result.Nodes.getNodeAs<Expr>("smart_pointer"); + + if (IsPtrToPtr && IsMemberExpr) { + // Ignore this case (eg. Foo->get()->DoSomething()); + return; + } + + auto SR = GetCall->getSourceRange(); + // CXXDependentScopeMemberExpr source range does not include parens + // Extend the source range of the get call to account for them. + if (isa<CXXDependentScopeMemberExpr>(GetCall)) + SR.setEnd(Lexer::getLocForEndOfToken(SR.getEnd(), 0, *Result.SourceManager, + getLangOpts()) + .getLocWithOffset(1)); + + StringRef SmartptrText = Lexer::getSourceText( + CharSourceRange::getTokenRange(Smartptr->getSourceRange()), + *Result.SourceManager, getLangOpts()); + // Replace foo->get() with *foo, and foo.get() with foo. + std::string Replacement = Twine(IsPtrToPtr ? "*" : "", SmartptrText).str(); + diag(GetCall->getBeginLoc(), "redundant get() call on smart pointer") + << FixItHint::CreateReplacement(SR, Replacement); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantSmartptrGetCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantSmartptrGetCheck.h new file mode 100644 index 0000000000..ffe8df8857 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantSmartptrGetCheck.h @@ -0,0 +1,46 @@ +//===--- RedundantSmartptrGetCheck.h - clang-tidy ---------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSMARTPTRGETCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSMARTPTRGETCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Find and remove redundant calls to smart pointer's `.get()` method. +/// +/// Examples: +/// +/// \code +/// ptr.get()->Foo() ==> ptr->Foo() +/// *ptr.get() ==> *ptr +/// *ptr->get() ==> **ptr +/// \endcode +class RedundantSmartptrGetCheck : public ClangTidyCheck { +public: + RedundantSmartptrGetCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + const bool IgnoreMacros; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSMARTPTRGETCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantStringCStrCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantStringCStrCheck.cpp new file mode 100644 index 0000000000..960d5abcc9 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantStringCStrCheck.cpp @@ -0,0 +1,201 @@ +//===- RedundantStringCStrCheck.cpp - Check for redundant c_str calls -----===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements a check for redundant calls of c_str() on strings. +// +//===----------------------------------------------------------------------===// + +#include "RedundantStringCStrCheck.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +namespace { + +// Return true if expr needs to be put in parens when it is an argument of a +// prefix unary operator, e.g. when it is a binary or ternary operator +// syntactically. +bool needParensAfterUnaryOperator(const Expr &ExprNode) { + if (isa<clang::BinaryOperator>(&ExprNode) || + isa<clang::ConditionalOperator>(&ExprNode)) { + return true; + } + if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&ExprNode)) { + return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus && + Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call && + Op->getOperator() != OO_Subscript; + } + return false; +} + +// Format a pointer to an expression: prefix with '*' but simplify +// when it already begins with '&'. Return empty string on failure. +std::string +formatDereference(const ast_matchers::MatchFinder::MatchResult &Result, + const Expr &ExprNode) { + if (const auto *Op = dyn_cast<clang::UnaryOperator>(&ExprNode)) { + if (Op->getOpcode() == UO_AddrOf) { + // Strip leading '&'. + return std::string(tooling::fixit::getText( + *Op->getSubExpr()->IgnoreParens(), *Result.Context)); + } + } + StringRef Text = tooling::fixit::getText(ExprNode, *Result.Context); + + if (Text.empty()) + return std::string(); + // Add leading '*'. + if (needParensAfterUnaryOperator(ExprNode)) { + return (llvm::Twine("*(") + Text + ")").str(); + } + return (llvm::Twine("*") + Text).str(); +} + +AST_MATCHER(MaterializeTemporaryExpr, isBoundToLValue) { + return Node.isBoundToLvalueReference(); +} + +} // end namespace + +void RedundantStringCStrCheck::registerMatchers( + ast_matchers::MatchFinder *Finder) { + // Match expressions of type 'string' or 'string*'. + const auto StringDecl = type(hasUnqualifiedDesugaredType(recordType( + hasDeclaration(cxxRecordDecl(hasName("::std::basic_string")))))); + const auto StringExpr = + expr(anyOf(hasType(StringDecl), hasType(qualType(pointsTo(StringDecl))))); + + // Match string constructor. + const auto StringConstructorExpr = expr(anyOf( + cxxConstructExpr(argumentCountIs(1), + hasDeclaration(cxxMethodDecl(hasName("basic_string")))), + cxxConstructExpr( + argumentCountIs(2), + hasDeclaration(cxxMethodDecl(hasName("basic_string"))), + // If present, the second argument is the alloc object which must not + // be present explicitly. + hasArgument(1, cxxDefaultArgExpr())))); + + // Match string constructor. + const auto StringViewConstructorExpr = cxxConstructExpr( + argumentCountIs(1), + hasDeclaration(cxxMethodDecl(hasName("basic_string_view")))); + + // Match a call to the string 'c_str()' method. + const auto StringCStrCallExpr = + cxxMemberCallExpr(on(StringExpr.bind("arg")), + callee(memberExpr().bind("member")), + callee(cxxMethodDecl(hasAnyName("c_str", "data")))) + .bind("call"); + const auto HasRValueTempParent = + hasParent(materializeTemporaryExpr(unless(isBoundToLValue()))); + // Detect redundant 'c_str()' calls through a string constructor. + // If CxxConstructExpr is the part of some CallExpr we need to + // check that matched ParamDecl of the ancestor CallExpr is not rvalue. + Finder->addMatcher( + traverse( + TK_AsIs, + cxxConstructExpr( + anyOf(StringConstructorExpr, StringViewConstructorExpr), + hasArgument(0, StringCStrCallExpr), + unless(anyOf(HasRValueTempParent, hasParent(cxxBindTemporaryExpr( + HasRValueTempParent)))))), + this); + + // Detect: 's == str.c_str()' -> 's == str' + Finder->addMatcher( + cxxOperatorCallExpr( + hasAnyOverloadedOperatorName("<", ">", ">=", "<=", "!=", "==", "+"), + anyOf(allOf(hasArgument(0, StringExpr), + hasArgument(1, StringCStrCallExpr)), + allOf(hasArgument(0, StringCStrCallExpr), + hasArgument(1, StringExpr)))), + this); + + // Detect: 'dst += str.c_str()' -> 'dst += str' + // Detect: 's = str.c_str()' -> 's = str' + Finder->addMatcher( + cxxOperatorCallExpr(hasAnyOverloadedOperatorName("=", "+="), + hasArgument(0, StringExpr), + hasArgument(1, StringCStrCallExpr)), + this); + + // Detect: 'dst.append(str.c_str())' -> 'dst.append(str)' + Finder->addMatcher( + cxxMemberCallExpr(on(StringExpr), callee(decl(cxxMethodDecl(hasAnyName( + "append", "assign", "compare")))), + argumentCountIs(1), hasArgument(0, StringCStrCallExpr)), + this); + + // Detect: 'dst.compare(p, n, str.c_str())' -> 'dst.compare(p, n, str)' + Finder->addMatcher( + cxxMemberCallExpr(on(StringExpr), + callee(decl(cxxMethodDecl(hasName("compare")))), + argumentCountIs(3), hasArgument(2, StringCStrCallExpr)), + this); + + // Detect: 'dst.find(str.c_str())' -> 'dst.find(str)' + Finder->addMatcher( + cxxMemberCallExpr(on(StringExpr), + callee(decl(cxxMethodDecl(hasAnyName( + "find", "find_first_not_of", "find_first_of", + "find_last_not_of", "find_last_of", "rfind")))), + anyOf(argumentCountIs(1), argumentCountIs(2)), + hasArgument(0, StringCStrCallExpr)), + this); + + // Detect: 'dst.insert(pos, str.c_str())' -> 'dst.insert(pos, str)' + Finder->addMatcher( + cxxMemberCallExpr(on(StringExpr), + callee(decl(cxxMethodDecl(hasName("insert")))), + argumentCountIs(2), hasArgument(1, StringCStrCallExpr)), + this); + + // Detect redundant 'c_str()' calls through a StringRef constructor. + Finder->addMatcher( + traverse( + TK_AsIs, + cxxConstructExpr( + // Implicit constructors of these classes are overloaded + // wrt. string types and they internally make a StringRef + // referring to the argument. Passing a string directly to + // them is preferred to passing a char pointer. + hasDeclaration(cxxMethodDecl(hasAnyName( + "::llvm::StringRef::StringRef", "::llvm::Twine::Twine"))), + argumentCountIs(1), + // The only argument must have the form x.c_str() or p->c_str() + // where the method is string::c_str(). StringRef also has + // a constructor from string which is more efficient (avoids + // strlen), so we can construct StringRef from the string + // directly. + hasArgument(0, StringCStrCallExpr))), + this); +} + +void RedundantStringCStrCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call"); + const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg"); + const auto *Member = Result.Nodes.getNodeAs<MemberExpr>("member"); + bool Arrow = Member->isArrow(); + // Replace the "call" node with the "arg" node, prefixed with '*' + // if the call was using '->' rather than '.'. + std::string ArgText = + Arrow ? formatDereference(Result, *Arg) + : tooling::fixit::getText(*Arg, *Result.Context).str(); + if (ArgText.empty()) + return; + + diag(Call->getBeginLoc(), "redundant call to %0") + << Member->getMemberDecl() + << FixItHint::CreateReplacement(Call->getSourceRange(), ArgText); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantStringCStrCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantStringCStrCheck.h new file mode 100644 index 0000000000..662fb955fc --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantStringCStrCheck.h @@ -0,0 +1,30 @@ +//===--- RedundantStringCStrCheck.h - clang-tidy ----------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSTRINGCSTRCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSTRINGCSTRCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Finds unnecessary calls to `std::string::c_str()`. +class RedundantStringCStrCheck : public ClangTidyCheck { +public: + RedundantStringCStrCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTSTRINGCSTRCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantStringInitCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantStringInitCheck.cpp new file mode 100644 index 0000000000..b579aafe8e --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantStringInitCheck.cpp @@ -0,0 +1,163 @@ +//===- RedundantStringInitCheck.cpp - clang-tidy ----------------*- C++ -*-===// +// +// 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 "RedundantStringInitCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include <optional> + +using namespace clang::ast_matchers; +using namespace clang::tidy::matchers; + +namespace clang::tidy::readability { + +const char DefaultStringNames[] = + "::std::basic_string_view;::std::basic_string"; + +static std::vector<StringRef> removeNamespaces(ArrayRef<StringRef> Names) { + std::vector<StringRef> Result; + Result.reserve(Names.size()); + for (StringRef Name : Names) { + StringRef::size_type ColonPos = Name.rfind(':'); + Result.push_back( + Name.drop_front(ColonPos == StringRef::npos ? 0 : ColonPos + 1)); + } + return Result; +} + +static const CXXConstructExpr * +getConstructExpr(const CXXCtorInitializer &CtorInit) { + const Expr *InitExpr = CtorInit.getInit(); + if (const auto *CleanUpExpr = dyn_cast<ExprWithCleanups>(InitExpr)) + InitExpr = CleanUpExpr->getSubExpr(); + return dyn_cast<CXXConstructExpr>(InitExpr); +} + +static std::optional<SourceRange> +getConstructExprArgRange(const CXXConstructExpr &Construct) { + SourceLocation B, E; + for (const Expr *Arg : Construct.arguments()) { + if (B.isInvalid()) + B = Arg->getBeginLoc(); + if (Arg->getEndLoc().isValid()) + E = Arg->getEndLoc(); + } + if (B.isInvalid() || E.isInvalid()) + return std::nullopt; + return SourceRange(B, E); +} + +RedundantStringInitCheck::RedundantStringInitCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + StringNames(utils::options::parseStringList( + Options.get("StringNames", DefaultStringNames))) {} + +void RedundantStringInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "StringNames", DefaultStringNames); +} + +void RedundantStringInitCheck::registerMatchers(MatchFinder *Finder) { + const auto HasStringTypeName = hasAnyName(StringNames); + const auto HasStringCtorName = hasAnyName(removeNamespaces(StringNames)); + + // Match string constructor. + const auto StringConstructorExpr = expr( + anyOf(cxxConstructExpr(argumentCountIs(1), + hasDeclaration(cxxMethodDecl(HasStringCtorName))), + // If present, the second argument is the alloc object which must + // not be present explicitly. + cxxConstructExpr(argumentCountIs(2), + hasDeclaration(cxxMethodDecl(HasStringCtorName)), + hasArgument(1, cxxDefaultArgExpr())))); + + // Match a string constructor expression with an empty string literal. + const auto EmptyStringCtorExpr = cxxConstructExpr( + StringConstructorExpr, + hasArgument(0, ignoringParenImpCasts(stringLiteral(hasSize(0))))); + + const auto EmptyStringCtorExprWithTemporaries = + cxxConstructExpr(StringConstructorExpr, + hasArgument(0, ignoringImplicit(EmptyStringCtorExpr))); + + const auto StringType = hasType(hasUnqualifiedDesugaredType( + recordType(hasDeclaration(cxxRecordDecl(HasStringTypeName))))); + const auto EmptyStringInit = traverse( + TK_AsIs, expr(ignoringImplicit(anyOf( + EmptyStringCtorExpr, EmptyStringCtorExprWithTemporaries)))); + + // Match a variable declaration with an empty string literal as initializer. + // Examples: + // string foo = ""; + // string bar(""); + Finder->addMatcher( + traverse(TK_AsIs, + namedDecl(varDecl(StringType, hasInitializer(EmptyStringInit)) + .bind("vardecl"), + unless(parmVarDecl()))), + this); + // Match a field declaration with an empty string literal as initializer. + Finder->addMatcher( + namedDecl(fieldDecl(StringType, hasInClassInitializer(EmptyStringInit)) + .bind("fieldDecl")), + this); + // Matches Constructor Initializers with an empty string literal as + // initializer. + // Examples: + // Foo() : SomeString("") {} + Finder->addMatcher( + cxxCtorInitializer( + isWritten(), + forField(allOf(StringType, optionally(hasInClassInitializer( + EmptyStringInit.bind("empty_init"))))), + withInitializer(EmptyStringInit)) + .bind("ctorInit"), + this); +} + +void RedundantStringInitCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *VDecl = Result.Nodes.getNodeAs<VarDecl>("vardecl")) { + // VarDecl's getSourceRange() spans 'string foo = ""' or 'string bar("")'. + // So start at getLocation() to span just 'foo = ""' or 'bar("")'. + SourceRange ReplaceRange(VDecl->getLocation(), VDecl->getEndLoc()); + diag(VDecl->getLocation(), "redundant string initialization") + << FixItHint::CreateReplacement(ReplaceRange, VDecl->getName()); + } + if (const auto *FDecl = Result.Nodes.getNodeAs<FieldDecl>("fieldDecl")) { + // FieldDecl's getSourceRange() spans 'string foo = ""'. + // So start at getLocation() to span just 'foo = ""'. + SourceRange ReplaceRange(FDecl->getLocation(), FDecl->getEndLoc()); + diag(FDecl->getLocation(), "redundant string initialization") + << FixItHint::CreateReplacement(ReplaceRange, FDecl->getName()); + } + if (const auto *CtorInit = + Result.Nodes.getNodeAs<CXXCtorInitializer>("ctorInit")) { + if (const FieldDecl *Member = CtorInit->getMember()) { + if (!Member->hasInClassInitializer() || + Result.Nodes.getNodeAs<Expr>("empty_init")) { + // The String isn't declared in the class with an initializer or its + // declared with a redundant initializer, which will be removed. Either + // way the string will be default initialized, therefore we can remove + // the constructor initializer entirely. + diag(CtorInit->getMemberLocation(), "redundant string initialization") + << FixItHint::CreateRemoval(CtorInit->getSourceRange()); + return; + } + } + const CXXConstructExpr *Construct = getConstructExpr(*CtorInit); + if (!Construct) + return; + if (std::optional<SourceRange> RemovalRange = + getConstructExprArgRange(*Construct)) + diag(CtorInit->getMemberLocation(), "redundant string initialization") + << FixItHint::CreateRemoval(*RemovalRange); + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantStringInitCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantStringInitCheck.h new file mode 100644 index 0000000000..853ea2fcd0 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/RedundantStringInitCheck.h @@ -0,0 +1,35 @@ +//===- RedundantStringInitCheck.h - clang-tidy ------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_STRING_INIT_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_STRING_INIT_H + +#include "../ClangTidyCheck.h" +#include <string> +#include <vector> + +namespace clang::tidy::readability { + +/// Finds unnecessary string initializations. +class RedundantStringInitCheck : public ClangTidyCheck { +public: + RedundantStringInitCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + std::vector<StringRef> StringNames; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANT_STRING_INIT_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp new file mode 100644 index 0000000000..07cb07f053 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/SimplifyBooleanExprCheck.cpp @@ -0,0 +1,958 @@ +//===-- SimplifyBooleanExprCheck.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 "SimplifyBooleanExprCheck.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Lex/Lexer.h" +#include "llvm/Support/SaveAndRestore.h" + +#include <optional> +#include <string> +#include <utility> + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +namespace { + +StringRef getText(const ASTContext &Context, SourceRange Range) { + return Lexer::getSourceText(CharSourceRange::getTokenRange(Range), + Context.getSourceManager(), + Context.getLangOpts()); +} + +template <typename T> StringRef getText(const ASTContext &Context, T &Node) { + return getText(Context, Node.getSourceRange()); +} + +} // namespace + +static constexpr char SimplifyOperatorDiagnostic[] = + "redundant boolean literal supplied to boolean operator"; +static constexpr char SimplifyConditionDiagnostic[] = + "redundant boolean literal in if statement condition"; +static constexpr char SimplifyConditionalReturnDiagnostic[] = + "redundant boolean literal in conditional return statement"; + +static bool needsParensAfterUnaryNegation(const Expr *E) { + E = E->IgnoreImpCasts(); + if (isa<BinaryOperator>(E) || isa<ConditionalOperator>(E)) + return true; + + if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E)) + return Op->getNumArgs() == 2 && Op->getOperator() != OO_Call && + Op->getOperator() != OO_Subscript; + + return false; +} + +static std::pair<BinaryOperatorKind, BinaryOperatorKind> Opposites[] = { + {BO_LT, BO_GE}, {BO_GT, BO_LE}, {BO_EQ, BO_NE}}; + +static StringRef negatedOperator(const BinaryOperator *BinOp) { + const BinaryOperatorKind Opcode = BinOp->getOpcode(); + for (auto NegatableOp : Opposites) { + if (Opcode == NegatableOp.first) + return BinOp->getOpcodeStr(NegatableOp.second); + if (Opcode == NegatableOp.second) + return BinOp->getOpcodeStr(NegatableOp.first); + } + return {}; +} + +static std::pair<OverloadedOperatorKind, StringRef> OperatorNames[] = { + {OO_EqualEqual, "=="}, {OO_ExclaimEqual, "!="}, {OO_Less, "<"}, + {OO_GreaterEqual, ">="}, {OO_Greater, ">"}, {OO_LessEqual, "<="}}; + +static StringRef getOperatorName(OverloadedOperatorKind OpKind) { + for (auto Name : OperatorNames) { + if (Name.first == OpKind) + return Name.second; + } + + return {}; +} + +static std::pair<OverloadedOperatorKind, OverloadedOperatorKind> + OppositeOverloads[] = {{OO_EqualEqual, OO_ExclaimEqual}, + {OO_Less, OO_GreaterEqual}, + {OO_Greater, OO_LessEqual}}; + +static StringRef negatedOperator(const CXXOperatorCallExpr *OpCall) { + const OverloadedOperatorKind Opcode = OpCall->getOperator(); + for (auto NegatableOp : OppositeOverloads) { + if (Opcode == NegatableOp.first) + return getOperatorName(NegatableOp.second); + if (Opcode == NegatableOp.second) + return getOperatorName(NegatableOp.first); + } + return {}; +} + +static std::string asBool(StringRef Text, bool NeedsStaticCast) { + if (NeedsStaticCast) + return ("static_cast<bool>(" + Text + ")").str(); + + return std::string(Text); +} + +static bool needsNullPtrComparison(const Expr *E) { + if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) + return ImpCast->getCastKind() == CK_PointerToBoolean || + ImpCast->getCastKind() == CK_MemberPointerToBoolean; + + return false; +} + +static bool needsZeroComparison(const Expr *E) { + if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) + return ImpCast->getCastKind() == CK_IntegralToBoolean; + + return false; +} + +static bool needsStaticCast(const Expr *E) { + if (const auto *ImpCast = dyn_cast<ImplicitCastExpr>(E)) { + if (ImpCast->getCastKind() == CK_UserDefinedConversion && + ImpCast->getSubExpr()->getType()->isBooleanType()) { + if (const auto *MemCall = + dyn_cast<CXXMemberCallExpr>(ImpCast->getSubExpr())) { + if (const auto *MemDecl = + dyn_cast<CXXConversionDecl>(MemCall->getMethodDecl())) { + if (MemDecl->isExplicit()) + return true; + } + } + } + } + + E = E->IgnoreImpCasts(); + return !E->getType()->isBooleanType(); +} + +static std::string compareExpressionToConstant(const ASTContext &Context, + const Expr *E, bool Negated, + const char *Constant) { + E = E->IgnoreImpCasts(); + const std::string ExprText = + (isa<BinaryOperator>(E) ? ("(" + getText(Context, *E) + ")") + : getText(Context, *E)) + .str(); + return ExprText + " " + (Negated ? "!=" : "==") + " " + Constant; +} + +static std::string compareExpressionToNullPtr(const ASTContext &Context, + const Expr *E, bool Negated) { + const char *NullPtr = Context.getLangOpts().CPlusPlus11 ? "nullptr" : "NULL"; + return compareExpressionToConstant(Context, E, Negated, NullPtr); +} + +static std::string compareExpressionToZero(const ASTContext &Context, + const Expr *E, bool Negated) { + return compareExpressionToConstant(Context, E, Negated, "0"); +} + +static std::string replacementExpression(const ASTContext &Context, + bool Negated, const Expr *E) { + E = E->IgnoreParenBaseCasts(); + if (const auto *EC = dyn_cast<ExprWithCleanups>(E)) + E = EC->getSubExpr(); + + const bool NeedsStaticCast = needsStaticCast(E); + if (Negated) { + if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) { + if (UnOp->getOpcode() == UO_LNot) { + if (needsNullPtrComparison(UnOp->getSubExpr())) + return compareExpressionToNullPtr(Context, UnOp->getSubExpr(), true); + + if (needsZeroComparison(UnOp->getSubExpr())) + return compareExpressionToZero(Context, UnOp->getSubExpr(), true); + + return replacementExpression(Context, false, UnOp->getSubExpr()); + } + } + + if (needsNullPtrComparison(E)) + return compareExpressionToNullPtr(Context, E, false); + + if (needsZeroComparison(E)) + return compareExpressionToZero(Context, E, false); + + StringRef NegatedOperator; + const Expr *LHS = nullptr; + const Expr *RHS = nullptr; + if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) { + NegatedOperator = negatedOperator(BinOp); + LHS = BinOp->getLHS(); + RHS = BinOp->getRHS(); + } else if (const auto *OpExpr = dyn_cast<CXXOperatorCallExpr>(E)) { + if (OpExpr->getNumArgs() == 2) { + NegatedOperator = negatedOperator(OpExpr); + LHS = OpExpr->getArg(0); + RHS = OpExpr->getArg(1); + } + } + if (!NegatedOperator.empty() && LHS && RHS) + return (asBool((getText(Context, *LHS) + " " + NegatedOperator + " " + + getText(Context, *RHS)) + .str(), + NeedsStaticCast)); + + StringRef Text = getText(Context, *E); + if (!NeedsStaticCast && needsParensAfterUnaryNegation(E)) + return ("!(" + Text + ")").str(); + + if (needsNullPtrComparison(E)) + return compareExpressionToNullPtr(Context, E, false); + + if (needsZeroComparison(E)) + return compareExpressionToZero(Context, E, false); + + return ("!" + asBool(Text, NeedsStaticCast)); + } + + if (const auto *UnOp = dyn_cast<UnaryOperator>(E)) { + if (UnOp->getOpcode() == UO_LNot) { + if (needsNullPtrComparison(UnOp->getSubExpr())) + return compareExpressionToNullPtr(Context, UnOp->getSubExpr(), false); + + if (needsZeroComparison(UnOp->getSubExpr())) + return compareExpressionToZero(Context, UnOp->getSubExpr(), false); + } + } + + if (needsNullPtrComparison(E)) + return compareExpressionToNullPtr(Context, E, true); + + if (needsZeroComparison(E)) + return compareExpressionToZero(Context, E, true); + + return asBool(getText(Context, *E), NeedsStaticCast); +} + +static bool containsDiscardedTokens(const ASTContext &Context, + CharSourceRange CharRange) { + std::string ReplacementText = + Lexer::getSourceText(CharRange, Context.getSourceManager(), + Context.getLangOpts()) + .str(); + Lexer Lex(CharRange.getBegin(), Context.getLangOpts(), ReplacementText.data(), + ReplacementText.data(), + ReplacementText.data() + ReplacementText.size()); + Lex.SetCommentRetentionState(true); + + Token Tok; + while (!Lex.LexFromRawLexer(Tok)) { + if (Tok.is(tok::TokenKind::comment) || Tok.is(tok::TokenKind::hash)) + return true; + } + + return false; +} + +class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> { + using Base = RecursiveASTVisitor<Visitor>; + +public: + Visitor(SimplifyBooleanExprCheck *Check, ASTContext &Context) + : Check(Check), Context(Context) {} + + bool traverse() { return TraverseAST(Context); } + + static bool shouldIgnore(Stmt *S) { + switch (S->getStmtClass()) { + case Stmt::ImplicitCastExprClass: + case Stmt::MaterializeTemporaryExprClass: + case Stmt::CXXBindTemporaryExprClass: + return true; + default: + return false; + } + } + + bool dataTraverseStmtPre(Stmt *S) { + if (S && !shouldIgnore(S)) + StmtStack.push_back(S); + return true; + } + + bool dataTraverseStmtPost(Stmt *S) { + if (S && !shouldIgnore(S)) { + assert(StmtStack.back() == S); + StmtStack.pop_back(); + } + return true; + } + + bool VisitBinaryOperator(const BinaryOperator *Op) const { + Check->reportBinOp(Context, Op); + return true; + } + + // Extracts a bool if an expression is (true|false|!true|!false); + static std::optional<bool> getAsBoolLiteral(const Expr *E, bool FilterMacro) { + if (const auto *Bool = dyn_cast<CXXBoolLiteralExpr>(E)) { + if (FilterMacro && Bool->getBeginLoc().isMacroID()) + return std::nullopt; + return Bool->getValue(); + } + if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E)) { + if (FilterMacro && UnaryOp->getBeginLoc().isMacroID()) + return std::nullopt; + if (UnaryOp->getOpcode() == UO_LNot) + if (std::optional<bool> Res = getAsBoolLiteral( + UnaryOp->getSubExpr()->IgnoreImplicit(), FilterMacro)) + return !*Res; + } + return std::nullopt; + } + + template <typename Node> struct NodeAndBool { + const Node *Item = nullptr; + bool Bool = false; + + operator bool() const { return Item != nullptr; } + }; + + using ExprAndBool = NodeAndBool<Expr>; + using DeclAndBool = NodeAndBool<Decl>; + + /// Detect's return (true|false|!true|!false); + static ExprAndBool parseReturnLiteralBool(const Stmt *S) { + const auto *RS = dyn_cast<ReturnStmt>(S); + if (!RS || !RS->getRetValue()) + return {}; + if (std::optional<bool> Ret = + getAsBoolLiteral(RS->getRetValue()->IgnoreImplicit(), false)) { + return {RS->getRetValue(), *Ret}; + } + return {}; + } + + /// If \p S is not a \c CompoundStmt, applies F on \p S, otherwise if there is + /// only 1 statement in the \c CompoundStmt, applies F on that single + /// statement. + template <typename Functor> + static auto checkSingleStatement(Stmt *S, Functor F) -> decltype(F(S)) { + if (auto *CS = dyn_cast<CompoundStmt>(S)) { + if (CS->size() == 1) + return F(CS->body_front()); + return {}; + } + return F(S); + } + + Stmt *parent() const { + return StmtStack.size() < 2 ? nullptr : StmtStack[StmtStack.size() - 2]; + } + + bool VisitIfStmt(IfStmt *If) { + // Skip any if's that have a condition var or an init statement, or are + // "if consteval" statements. + if (If->hasInitStorage() || If->hasVarStorage() || If->isConsteval()) + return true; + /* + * if (true) ThenStmt(); -> ThenStmt(); + * if (false) ThenStmt(); -> <Empty>; + * if (false) ThenStmt(); else ElseStmt() -> ElseStmt(); + */ + Expr *Cond = If->getCond()->IgnoreImplicit(); + if (std::optional<bool> Bool = getAsBoolLiteral(Cond, true)) { + if (*Bool) + Check->replaceWithThenStatement(Context, If, Cond); + else + Check->replaceWithElseStatement(Context, If, Cond); + } + + if (If->getElse()) { + /* + * if (Cond) return true; else return false; -> return Cond; + * if (Cond) return false; else return true; -> return !Cond; + */ + if (ExprAndBool ThenReturnBool = + checkSingleStatement(If->getThen(), parseReturnLiteralBool)) { + ExprAndBool ElseReturnBool = + checkSingleStatement(If->getElse(), parseReturnLiteralBool); + if (ElseReturnBool && ThenReturnBool.Bool != ElseReturnBool.Bool) { + if (Check->ChainedConditionalReturn || + !isa_and_nonnull<IfStmt>(parent())) { + Check->replaceWithReturnCondition(Context, If, ThenReturnBool.Item, + ElseReturnBool.Bool); + } + } + } else { + /* + * if (Cond) A = true; else A = false; -> A = Cond; + * if (Cond) A = false; else A = true; -> A = !Cond; + */ + Expr *Var = nullptr; + SourceLocation Loc; + auto VarBoolAssignmentMatcher = [&Var, + &Loc](const Stmt *S) -> DeclAndBool { + const auto *BO = dyn_cast<BinaryOperator>(S); + if (!BO || BO->getOpcode() != BO_Assign) + return {}; + std::optional<bool> RightasBool = + getAsBoolLiteral(BO->getRHS()->IgnoreImplicit(), false); + if (!RightasBool) + return {}; + Expr *IgnImp = BO->getLHS()->IgnoreImplicit(); + if (!Var) { + // We only need to track these for the Then branch. + Loc = BO->getRHS()->getBeginLoc(); + Var = IgnImp; + } + if (auto *DRE = dyn_cast<DeclRefExpr>(IgnImp)) + return {DRE->getDecl(), *RightasBool}; + if (auto *ME = dyn_cast<MemberExpr>(IgnImp)) + return {ME->getMemberDecl(), *RightasBool}; + return {}; + }; + if (DeclAndBool ThenAssignment = + checkSingleStatement(If->getThen(), VarBoolAssignmentMatcher)) { + DeclAndBool ElseAssignment = + checkSingleStatement(If->getElse(), VarBoolAssignmentMatcher); + if (ElseAssignment.Item == ThenAssignment.Item && + ElseAssignment.Bool != ThenAssignment.Bool) { + if (Check->ChainedConditionalAssignment || + !isa_and_nonnull<IfStmt>(parent())) { + Check->replaceWithAssignment(Context, If, Var, Loc, + ElseAssignment.Bool); + } + } + } + } + } + return true; + } + + bool VisitConditionalOperator(ConditionalOperator *Cond) { + /* + * Condition ? true : false; -> Condition + * Condition ? false : true; -> !Condition; + */ + if (std::optional<bool> Then = + getAsBoolLiteral(Cond->getTrueExpr()->IgnoreImplicit(), false)) { + if (std::optional<bool> Else = + getAsBoolLiteral(Cond->getFalseExpr()->IgnoreImplicit(), false)) { + if (*Then != *Else) + Check->replaceWithCondition(Context, Cond, *Else); + } + } + return true; + } + + bool VisitCompoundStmt(CompoundStmt *CS) { + if (CS->size() < 2) + return true; + bool CurIf = false, PrevIf = false; + for (auto First = CS->body_begin(), Second = std::next(First), + End = CS->body_end(); + Second != End; ++Second, ++First) { + PrevIf = CurIf; + CurIf = isa<IfStmt>(*First); + ExprAndBool TrailingReturnBool = parseReturnLiteralBool(*Second); + if (!TrailingReturnBool) + continue; + + if (CurIf) { + /* + * if (Cond) return true; return false; -> return Cond; + * if (Cond) return false; return true; -> return !Cond; + */ + auto *If = cast<IfStmt>(*First); + if (!If->hasInitStorage() && !If->hasVarStorage() && + !If->isConsteval()) { + ExprAndBool ThenReturnBool = + checkSingleStatement(If->getThen(), parseReturnLiteralBool); + if (ThenReturnBool && + ThenReturnBool.Bool != TrailingReturnBool.Bool) { + if ((Check->ChainedConditionalReturn || !PrevIf) && + If->getElse() == nullptr) { + Check->replaceCompoundReturnWithCondition( + Context, cast<ReturnStmt>(*Second), TrailingReturnBool.Bool, + If, ThenReturnBool.Item); + } + } + } + } else if (isa<LabelStmt, CaseStmt, DefaultStmt>(*First)) { + /* + * (case X|label_X|default): if (Cond) return BoolLiteral; + * return !BoolLiteral + */ + Stmt *SubStmt = + isa<LabelStmt>(*First) ? cast<LabelStmt>(*First)->getSubStmt() + : isa<CaseStmt>(*First) ? cast<CaseStmt>(*First)->getSubStmt() + : cast<DefaultStmt>(*First)->getSubStmt(); + auto *SubIf = dyn_cast<IfStmt>(SubStmt); + if (SubIf && !SubIf->getElse() && !SubIf->hasInitStorage() && + !SubIf->hasVarStorage() && !SubIf->isConsteval()) { + ExprAndBool ThenReturnBool = + checkSingleStatement(SubIf->getThen(), parseReturnLiteralBool); + if (ThenReturnBool && + ThenReturnBool.Bool != TrailingReturnBool.Bool) { + Check->replaceCompoundReturnWithCondition( + Context, cast<ReturnStmt>(*Second), TrailingReturnBool.Bool, + SubIf, ThenReturnBool.Item); + } + } + } + } + return true; + } + + static bool isUnaryLNot(const Expr *E) { + return isa<UnaryOperator>(E) && + cast<UnaryOperator>(E)->getOpcode() == UO_LNot; + } + + template <typename Functor> + static bool checkEitherSide(const BinaryOperator *BO, Functor Func) { + return Func(BO->getLHS()) || Func(BO->getRHS()); + } + + static bool nestedDemorgan(const Expr *E, unsigned NestingLevel) { + const auto *BO = dyn_cast<BinaryOperator>(E->IgnoreUnlessSpelledInSource()); + if (!BO) + return false; + if (!BO->getType()->isBooleanType()) + return false; + switch (BO->getOpcode()) { + case BO_LT: + case BO_GT: + case BO_LE: + case BO_GE: + case BO_EQ: + case BO_NE: + return true; + case BO_LAnd: + case BO_LOr: + if (checkEitherSide(BO, isUnaryLNot)) + return true; + if (NestingLevel) { + if (checkEitherSide(BO, [NestingLevel](const Expr *E) { + return nestedDemorgan(E, NestingLevel - 1); + })) + return true; + } + return false; + default: + return false; + } + } + + bool TraverseUnaryOperator(UnaryOperator *Op) { + if (!Check->SimplifyDeMorgan || Op->getOpcode() != UO_LNot) + return Base::TraverseUnaryOperator(Op); + Expr *SubImp = Op->getSubExpr()->IgnoreImplicit(); + auto *Parens = dyn_cast<ParenExpr>(SubImp); + auto *BinaryOp = + Parens + ? dyn_cast<BinaryOperator>(Parens->getSubExpr()->IgnoreImplicit()) + : dyn_cast<BinaryOperator>(SubImp); + if (!BinaryOp || !BinaryOp->isLogicalOp() || + !BinaryOp->getType()->isBooleanType()) + return Base::TraverseUnaryOperator(Op); + if (Check->SimplifyDeMorganRelaxed || + checkEitherSide(BinaryOp, isUnaryLNot) || + checkEitherSide(BinaryOp, + [](const Expr *E) { return nestedDemorgan(E, 1); })) { + if (Check->reportDeMorgan(Context, Op, BinaryOp, !IsProcessing, parent(), + Parens) && + !Check->areDiagsSelfContained()) { + llvm::SaveAndRestore RAII(IsProcessing, true); + return Base::TraverseUnaryOperator(Op); + } + } + return Base::TraverseUnaryOperator(Op); + } + +private: + bool IsProcessing = false; + SimplifyBooleanExprCheck *Check; + SmallVector<Stmt *, 32> StmtStack; + ASTContext &Context; +}; + +SimplifyBooleanExprCheck::SimplifyBooleanExprCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + ChainedConditionalReturn(Options.get("ChainedConditionalReturn", false)), + ChainedConditionalAssignment( + Options.get("ChainedConditionalAssignment", false)), + SimplifyDeMorgan(Options.get("SimplifyDeMorgan", true)), + SimplifyDeMorganRelaxed(Options.get("SimplifyDeMorganRelaxed", false)) { + if (SimplifyDeMorganRelaxed && !SimplifyDeMorgan) + configurationDiag("%0: 'SimplifyDeMorganRelaxed' cannot be enabled " + "without 'SimplifyDeMorgan' enabled") + << Name; +} + +static bool containsBoolLiteral(const Expr *E) { + if (!E) + return false; + E = E->IgnoreParenImpCasts(); + if (isa<CXXBoolLiteralExpr>(E)) + return true; + if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) + return containsBoolLiteral(BinOp->getLHS()) || + containsBoolLiteral(BinOp->getRHS()); + if (const auto *UnaryOp = dyn_cast<UnaryOperator>(E)) + return containsBoolLiteral(UnaryOp->getSubExpr()); + return false; +} + +void SimplifyBooleanExprCheck::reportBinOp(const ASTContext &Context, + const BinaryOperator *Op) { + const auto *LHS = Op->getLHS()->IgnoreParenImpCasts(); + const auto *RHS = Op->getRHS()->IgnoreParenImpCasts(); + + const CXXBoolLiteralExpr *Bool; + const Expr *Other; + if ((Bool = dyn_cast<CXXBoolLiteralExpr>(LHS)) != nullptr) + Other = RHS; + else if ((Bool = dyn_cast<CXXBoolLiteralExpr>(RHS)) != nullptr) + Other = LHS; + else + return; + + if (Bool->getBeginLoc().isMacroID()) + return; + + // FIXME: why do we need this? + if (!isa<CXXBoolLiteralExpr>(Other) && containsBoolLiteral(Other)) + return; + + bool BoolValue = Bool->getValue(); + + auto ReplaceWithExpression = [this, &Context, LHS, RHS, + Bool](const Expr *ReplaceWith, bool Negated) { + std::string Replacement = + replacementExpression(Context, Negated, ReplaceWith); + SourceRange Range(LHS->getBeginLoc(), RHS->getEndLoc()); + issueDiag(Context, Bool->getBeginLoc(), SimplifyOperatorDiagnostic, Range, + Replacement); + }; + + switch (Op->getOpcode()) { + case BO_LAnd: + if (BoolValue) + // expr && true -> expr + ReplaceWithExpression(Other, /*Negated=*/false); + else + // expr && false -> false + ReplaceWithExpression(Bool, /*Negated=*/false); + break; + case BO_LOr: + if (BoolValue) + // expr || true -> true + ReplaceWithExpression(Bool, /*Negated=*/false); + else + // expr || false -> expr + ReplaceWithExpression(Other, /*Negated=*/false); + break; + case BO_EQ: + // expr == true -> expr, expr == false -> !expr + ReplaceWithExpression(Other, /*Negated=*/!BoolValue); + break; + case BO_NE: + // expr != true -> !expr, expr != false -> expr + ReplaceWithExpression(Other, /*Negated=*/BoolValue); + break; + default: + break; + } +} + +void SimplifyBooleanExprCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ChainedConditionalReturn", ChainedConditionalReturn); + Options.store(Opts, "ChainedConditionalAssignment", + ChainedConditionalAssignment); + Options.store(Opts, "SimplifyDeMorgan", SimplifyDeMorgan); + Options.store(Opts, "SimplifyDeMorganRelaxed", SimplifyDeMorganRelaxed); +} + +void SimplifyBooleanExprCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(translationUnitDecl(), this); +} + +void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) { + Visitor(this, *Result.Context).traverse(); +} + +void SimplifyBooleanExprCheck::issueDiag(const ASTContext &Context, + SourceLocation Loc, + StringRef Description, + SourceRange ReplacementRange, + StringRef Replacement) { + CharSourceRange CharRange = + Lexer::makeFileCharRange(CharSourceRange::getTokenRange(ReplacementRange), + Context.getSourceManager(), getLangOpts()); + + DiagnosticBuilder Diag = diag(Loc, Description); + if (!containsDiscardedTokens(Context, CharRange)) + Diag << FixItHint::CreateReplacement(CharRange, Replacement); +} + +void SimplifyBooleanExprCheck::replaceWithThenStatement( + const ASTContext &Context, const IfStmt *IfStatement, + const Expr *BoolLiteral) { + issueDiag(Context, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic, + IfStatement->getSourceRange(), + getText(Context, *IfStatement->getThen())); +} + +void SimplifyBooleanExprCheck::replaceWithElseStatement( + const ASTContext &Context, const IfStmt *IfStatement, + const Expr *BoolLiteral) { + const Stmt *ElseStatement = IfStatement->getElse(); + issueDiag(Context, BoolLiteral->getBeginLoc(), SimplifyConditionDiagnostic, + IfStatement->getSourceRange(), + ElseStatement ? getText(Context, *ElseStatement) : ""); +} + +void SimplifyBooleanExprCheck::replaceWithCondition( + const ASTContext &Context, const ConditionalOperator *Ternary, + bool Negated) { + std::string Replacement = + replacementExpression(Context, Negated, Ternary->getCond()); + issueDiag(Context, Ternary->getTrueExpr()->getBeginLoc(), + "redundant boolean literal in ternary expression result", + Ternary->getSourceRange(), Replacement); +} + +void SimplifyBooleanExprCheck::replaceWithReturnCondition( + const ASTContext &Context, const IfStmt *If, const Expr *BoolLiteral, + bool Negated) { + StringRef Terminator = isa<CompoundStmt>(If->getElse()) ? ";" : ""; + std::string Condition = + replacementExpression(Context, Negated, If->getCond()); + std::string Replacement = ("return " + Condition + Terminator).str(); + SourceLocation Start = BoolLiteral->getBeginLoc(); + issueDiag(Context, Start, SimplifyConditionalReturnDiagnostic, + If->getSourceRange(), Replacement); +} + +void SimplifyBooleanExprCheck::replaceCompoundReturnWithCondition( + const ASTContext &Context, const ReturnStmt *Ret, bool Negated, + const IfStmt *If, const Expr *ThenReturn) { + const std::string Replacement = + "return " + replacementExpression(Context, Negated, If->getCond()); + issueDiag(Context, ThenReturn->getBeginLoc(), + SimplifyConditionalReturnDiagnostic, + SourceRange(If->getBeginLoc(), Ret->getEndLoc()), Replacement); +} + +void SimplifyBooleanExprCheck::replaceWithAssignment(const ASTContext &Context, + const IfStmt *IfAssign, + const Expr *Var, + SourceLocation Loc, + bool Negated) { + SourceRange Range = IfAssign->getSourceRange(); + StringRef VariableName = getText(Context, *Var); + StringRef Terminator = isa<CompoundStmt>(IfAssign->getElse()) ? ";" : ""; + std::string Condition = + replacementExpression(Context, Negated, IfAssign->getCond()); + std::string Replacement = + (VariableName + " = " + Condition + Terminator).str(); + issueDiag(Context, Loc, "redundant boolean literal in conditional assignment", + Range, Replacement); +} + +/// Swaps a \c BinaryOperator opcode from `&&` to `||` or vice-versa. +static bool flipDemorganOperator(llvm::SmallVectorImpl<FixItHint> &Output, + const BinaryOperator *BO) { + assert(BO->isLogicalOp()); + if (BO->getOperatorLoc().isMacroID()) + return true; + Output.push_back(FixItHint::CreateReplacement( + BO->getOperatorLoc(), BO->getOpcode() == BO_LAnd ? "||" : "&&")); + return false; +} + +static BinaryOperatorKind getDemorganFlippedOperator(BinaryOperatorKind BO) { + assert(BinaryOperator::isLogicalOp(BO)); + return BO == BO_LAnd ? BO_LOr : BO_LAnd; +} + +static bool flipDemorganSide(SmallVectorImpl<FixItHint> &Fixes, + const ASTContext &Ctx, const Expr *E, + std::optional<BinaryOperatorKind> OuterBO); + +/// Inverts \p BinOp, Removing \p Parens if they exist and are safe to remove. +/// returns \c true if there is any issue building the Fixes, \c false +/// otherwise. +static bool +flipDemorganBinaryOperator(SmallVectorImpl<FixItHint> &Fixes, + const ASTContext &Ctx, const BinaryOperator *BinOp, + std::optional<BinaryOperatorKind> OuterBO, + const ParenExpr *Parens = nullptr) { + switch (BinOp->getOpcode()) { + case BO_LAnd: + case BO_LOr: { + // if we have 'a && b' or 'a || b', use demorgan to flip it to '!a || !b' + // or '!a && !b'. + if (flipDemorganOperator(Fixes, BinOp)) + return true; + auto NewOp = getDemorganFlippedOperator(BinOp->getOpcode()); + if (OuterBO) { + // The inner parens are technically needed in a fix for + // `!(!A1 && !(A2 || A3)) -> (A1 || (A2 && A3))`, + // however this would trip the LogicalOpParentheses warning. + // FIXME: Make this user configurable or detect if that warning is + // enabled. + constexpr bool LogicalOpParentheses = true; + if (((*OuterBO == NewOp) || (!LogicalOpParentheses && + (*OuterBO == BO_LOr && NewOp == BO_LAnd))) && + Parens) { + if (!Parens->getLParen().isMacroID() && + !Parens->getRParen().isMacroID()) { + Fixes.push_back(FixItHint::CreateRemoval(Parens->getLParen())); + Fixes.push_back(FixItHint::CreateRemoval(Parens->getRParen())); + } + } + if (*OuterBO == BO_LAnd && NewOp == BO_LOr && !Parens) { + Fixes.push_back(FixItHint::CreateInsertion(BinOp->getBeginLoc(), "(")); + Fixes.push_back(FixItHint::CreateInsertion( + Lexer::getLocForEndOfToken(BinOp->getEndLoc(), 0, + Ctx.getSourceManager(), + Ctx.getLangOpts()), + ")")); + } + } + if (flipDemorganSide(Fixes, Ctx, BinOp->getLHS(), NewOp) || + flipDemorganSide(Fixes, Ctx, BinOp->getRHS(), NewOp)) + return true; + return false; + }; + case BO_LT: + case BO_GT: + case BO_LE: + case BO_GE: + case BO_EQ: + case BO_NE: + // For comparison operators, just negate the comparison. + if (BinOp->getOperatorLoc().isMacroID()) + return true; + Fixes.push_back(FixItHint::CreateReplacement( + BinOp->getOperatorLoc(), + BinaryOperator::getOpcodeStr( + BinaryOperator::negateComparisonOp(BinOp->getOpcode())))); + return false; + default: + // for any other binary operator, just use logical not and wrap in + // parens. + if (Parens) { + if (Parens->getBeginLoc().isMacroID()) + return true; + Fixes.push_back(FixItHint::CreateInsertion(Parens->getBeginLoc(), "!")); + } else { + if (BinOp->getBeginLoc().isMacroID() || BinOp->getEndLoc().isMacroID()) + return true; + Fixes.append({FixItHint::CreateInsertion(BinOp->getBeginLoc(), "!("), + FixItHint::CreateInsertion( + Lexer::getLocForEndOfToken(BinOp->getEndLoc(), 0, + Ctx.getSourceManager(), + Ctx.getLangOpts()), + ")")}); + } + break; + } + return false; +} + +static bool flipDemorganSide(SmallVectorImpl<FixItHint> &Fixes, + const ASTContext &Ctx, const Expr *E, + std::optional<BinaryOperatorKind> OuterBO) { + if (isa<UnaryOperator>(E) && cast<UnaryOperator>(E)->getOpcode() == UO_LNot) { + // if we have a not operator, '!a', just remove the '!'. + if (cast<UnaryOperator>(E)->getOperatorLoc().isMacroID()) + return true; + Fixes.push_back( + FixItHint::CreateRemoval(cast<UnaryOperator>(E)->getOperatorLoc())); + return false; + } + if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) { + return flipDemorganBinaryOperator(Fixes, Ctx, BinOp, OuterBO); + } + if (const auto *Paren = dyn_cast<ParenExpr>(E)) { + if (const auto *BinOp = dyn_cast<BinaryOperator>(Paren->getSubExpr())) { + return flipDemorganBinaryOperator(Fixes, Ctx, BinOp, OuterBO, Paren); + } + } + // Fallback case just insert a logical not operator. + if (E->getBeginLoc().isMacroID()) + return true; + Fixes.push_back(FixItHint::CreateInsertion(E->getBeginLoc(), "!")); + return false; +} + +static bool shouldRemoveParens(const Stmt *Parent, + BinaryOperatorKind NewOuterBinary, + const ParenExpr *Parens) { + if (!Parens) + return false; + if (!Parent) + return true; + switch (Parent->getStmtClass()) { + case Stmt::BinaryOperatorClass: { + const auto *BO = cast<BinaryOperator>(Parent); + if (BO->isAssignmentOp()) + return true; + if (BO->isCommaOp()) + return true; + if (BO->getOpcode() == NewOuterBinary) + return true; + return false; + } + case Stmt::UnaryOperatorClass: + case Stmt::CXXRewrittenBinaryOperatorClass: + return false; + default: + return true; + } +} + +bool SimplifyBooleanExprCheck::reportDeMorgan(const ASTContext &Context, + const UnaryOperator *Outer, + const BinaryOperator *Inner, + bool TryOfferFix, + const Stmt *Parent, + const ParenExpr *Parens) { + assert(Outer); + assert(Inner); + assert(Inner->isLogicalOp()); + + auto Diag = + diag(Outer->getBeginLoc(), + "boolean expression can be simplified by DeMorgan's theorem"); + Diag << Outer->getSourceRange(); + // If we have already fixed this with a previous fix, don't attempt any fixes + if (!TryOfferFix) + return false; + if (Outer->getOperatorLoc().isMacroID()) + return false; + SmallVector<FixItHint> Fixes; + auto NewOpcode = getDemorganFlippedOperator(Inner->getOpcode()); + if (shouldRemoveParens(Parent, NewOpcode, Parens)) { + Fixes.push_back(FixItHint::CreateRemoval( + SourceRange(Outer->getOperatorLoc(), Parens->getLParen()))); + Fixes.push_back(FixItHint::CreateRemoval(Parens->getRParen())); + } else { + Fixes.push_back(FixItHint::CreateRemoval(Outer->getOperatorLoc())); + } + if (flipDemorganOperator(Fixes, Inner)) + return false; + if (flipDemorganSide(Fixes, Context, Inner->getLHS(), NewOpcode) || + flipDemorganSide(Fixes, Context, Inner->getRHS(), NewOpcode)) + return false; + Diag << Fixes; + return true; +} +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/SimplifyBooleanExprCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/SimplifyBooleanExprCheck.h new file mode 100644 index 0000000000..f9ed331b7b --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/SimplifyBooleanExprCheck.h @@ -0,0 +1,75 @@ +//===--- SimplifyBooleanExpr.h clang-tidy -----------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFY_BOOLEAN_EXPR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFY_BOOLEAN_EXPR_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Looks for boolean expressions involving boolean constants and simplifies +/// them to use the appropriate boolean expression directly. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/simplify-boolean-expr.html +class SimplifyBooleanExprCheck : public ClangTidyCheck { +public: + SimplifyBooleanExprCheck(StringRef Name, ClangTidyContext *Context); + + void storeOptions(ClangTidyOptions::OptionMap &Options) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + class Visitor; + + void reportBinOp(const ASTContext &Context, const BinaryOperator *Op); + + void replaceWithThenStatement(const ASTContext &Context, + const IfStmt *IfStatement, + const Expr *BoolLiteral); + + void replaceWithElseStatement(const ASTContext &Context, + const IfStmt *IfStatement, + const Expr *BoolLiteral); + + void replaceWithCondition(const ASTContext &Context, + const ConditionalOperator *Ternary, bool Negated); + + void replaceWithReturnCondition(const ASTContext &Context, const IfStmt *If, + const Expr *BoolLiteral, bool Negated); + + void replaceWithAssignment(const ASTContext &Context, const IfStmt *If, + const Expr *Var, SourceLocation Loc, bool Negated); + + void replaceCompoundReturnWithCondition(const ASTContext &Context, + const ReturnStmt *Ret, bool Negated, + const IfStmt *If, + const Expr *ThenReturn); + + bool reportDeMorgan(const ASTContext &Context, const UnaryOperator *Outer, + const BinaryOperator *Inner, bool TryOfferFix, + const Stmt *Parent, const ParenExpr *Parens); + + void issueDiag(const ASTContext &Result, SourceLocation Loc, + StringRef Description, SourceRange ReplacementRange, + StringRef Replacement); + + const bool ChainedConditionalReturn; + const bool ChainedConditionalAssignment; + const bool SimplifyDeMorgan; + const bool SimplifyDeMorganRelaxed; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFY_BOOLEAN_EXPR_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/SimplifySubscriptExprCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/SimplifySubscriptExprCheck.cpp new file mode 100644 index 0000000000..d274abcbfa --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/SimplifySubscriptExprCheck.cpp @@ -0,0 +1,67 @@ +//===--- SimplifySubscriptExprCheck.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 "SimplifySubscriptExprCheck.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +static const char KDefaultTypes[] = + "::std::basic_string;::std::basic_string_view;::std::vector;::std::array"; + +SimplifySubscriptExprCheck::SimplifySubscriptExprCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), Types(utils::options::parseStringList( + Options.get("Types", KDefaultTypes))) { +} + +void SimplifySubscriptExprCheck::registerMatchers(MatchFinder *Finder) { + const auto TypesMatcher = hasUnqualifiedDesugaredType( + recordType(hasDeclaration(cxxRecordDecl(hasAnyName(Types))))); + + Finder->addMatcher( + arraySubscriptExpr(hasBase( + cxxMemberCallExpr( + has(memberExpr().bind("member")), + on(hasType(qualType( + unless(anyOf(substTemplateTypeParmType(), + hasDescendant(substTemplateTypeParmType()))), + anyOf(TypesMatcher, pointerType(pointee(TypesMatcher)))))), + callee(namedDecl(hasName("data")))) + .bind("call"))), + this); +} + +void SimplifySubscriptExprCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call"); + if (Result.Context->getSourceManager().isMacroBodyExpansion( + Call->getExprLoc())) + return; + + const auto *Member = Result.Nodes.getNodeAs<MemberExpr>("member"); + auto DiagBuilder = + diag(Member->getMemberLoc(), + "accessing an element of the container does not require a call to " + "'data()'; did you mean to use 'operator[]'?"); + if (Member->isArrow()) + DiagBuilder << FixItHint::CreateInsertion(Member->getBeginLoc(), "(*") + << FixItHint::CreateInsertion(Member->getOperatorLoc(), ")"); + DiagBuilder << FixItHint::CreateRemoval( + {Member->getOperatorLoc(), Call->getEndLoc()}); +} + +void SimplifySubscriptExprCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "Types", utils::options::serializeStringList(Types)); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/SimplifySubscriptExprCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/SimplifySubscriptExprCheck.h new file mode 100644 index 0000000000..83bab16b2f --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/SimplifySubscriptExprCheck.h @@ -0,0 +1,39 @@ +//===--- SimplifySubscriptExprCheck.h - clang-tidy---------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFYSUBSCRIPTEXPRCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFYSUBSCRIPTEXPRCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Simplifies subscript expressions. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/simplify-subscript-expr.html +class SimplifySubscriptExprCheck : public ClangTidyCheck { +public: + SimplifySubscriptExprCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap& Opts) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + const std::vector<StringRef> Types; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SIMPLIFYSUBSCRIPTEXPRCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp new file mode 100644 index 0000000000..b23b67e626 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.cpp @@ -0,0 +1,95 @@ +//===--- StaticAccessedThroughInstanceCheck.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 "StaticAccessedThroughInstanceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/StringRef.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +static unsigned getNameSpecifierNestingLevel(const QualType &QType) { + if (const ElaboratedType *ElType = QType->getAs<ElaboratedType>()) { + if (const NestedNameSpecifier *NestedSpecifiers = ElType->getQualifier()) { + unsigned NameSpecifierNestingLevel = 1; + do { + NameSpecifierNestingLevel++; + NestedSpecifiers = NestedSpecifiers->getPrefix(); + } while (NestedSpecifiers); + + return NameSpecifierNestingLevel; + } + } + return 0; +} + +void StaticAccessedThroughInstanceCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "NameSpecifierNestingThreshold", + NameSpecifierNestingThreshold); +} + +void StaticAccessedThroughInstanceCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + memberExpr(hasDeclaration(anyOf(cxxMethodDecl(isStaticStorageClass()), + varDecl(hasStaticStorageDuration())))) + .bind("memberExpression"), + this); +} + +void StaticAccessedThroughInstanceCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MemberExpression = + Result.Nodes.getNodeAs<MemberExpr>("memberExpression"); + + if (MemberExpression->getBeginLoc().isMacroID()) + return; + + const Expr *BaseExpr = MemberExpression->getBase(); + + // Do not warn for overloaded -> operators. + if (isa<CXXOperatorCallExpr>(BaseExpr)) + return; + + QualType BaseType = + BaseExpr->getType()->isPointerType() + ? BaseExpr->getType()->getPointeeType().getUnqualifiedType() + : BaseExpr->getType().getUnqualifiedType(); + + const ASTContext *AstContext = Result.Context; + PrintingPolicy PrintingPolicyWithSupressedTag(AstContext->getLangOpts()); + PrintingPolicyWithSupressedTag.SuppressTagKeyword = true; + PrintingPolicyWithSupressedTag.SuppressUnwrittenScope = true; + + PrintingPolicyWithSupressedTag.PrintCanonicalTypes = + !BaseExpr->getType()->isTypedefNameType(); + + std::string BaseTypeName = + BaseType.getAsString(PrintingPolicyWithSupressedTag); + + // Do not warn for CUDA built-in variables. + if (StringRef(BaseTypeName).startswith("__cuda_builtin_")) + return; + + SourceLocation MemberExprStartLoc = MemberExpression->getBeginLoc(); + auto Diag = + diag(MemberExprStartLoc, "static member accessed through instance"); + + if (BaseExpr->HasSideEffects(*AstContext) || + getNameSpecifierNestingLevel(BaseType) > NameSpecifierNestingThreshold) + return; + + Diag << FixItHint::CreateReplacement( + CharSourceRange::getCharRange(MemberExprStartLoc, + MemberExpression->getMemberLoc()), + BaseTypeName + "::"); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.h new file mode 100644 index 0000000000..4044f13769 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/StaticAccessedThroughInstanceCheck.h @@ -0,0 +1,41 @@ +//===--- StaticAccessedThroughInstanceCheck.h - clang-tidy-------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATIC_ACCESSED_THROUGH_INSTANCE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATIC_ACCESSED_THROUGH_INSTANCE_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Checks for member expressions that access static members through +/// instances and replaces them with uses of the appropriate qualified-id. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/static-accessed-through-instance.html +class StaticAccessedThroughInstanceCheck : public ClangTidyCheck { +public: + StaticAccessedThroughInstanceCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + NameSpecifierNestingThreshold( + Options.get("NameSpecifierNestingThreshold", 3U)) {} + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + const unsigned NameSpecifierNestingThreshold; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATIC_ACCESSED_THROUGH_INSTANCE_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.cpp new file mode 100644 index 0000000000..3e6bb01b55 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.cpp @@ -0,0 +1,61 @@ +//===--- StaticDefinitionInAnonymousNamespaceCheck.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 "StaticDefinitionInAnonymousNamespaceCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +void StaticDefinitionInAnonymousNamespaceCheck::registerMatchers( + MatchFinder *Finder) { + Finder->addMatcher( + namedDecl(anyOf(functionDecl(isDefinition(), isStaticStorageClass()), + varDecl(isDefinition(), isStaticStorageClass())), + isInAnonymousNamespace()) + .bind("static-def"), + this); +} + +void StaticDefinitionInAnonymousNamespaceCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Def = Result.Nodes.getNodeAs<NamedDecl>("static-def"); + // Skips all static definitions defined in Macro. + if (Def->getLocation().isMacroID()) + return; + + // Skips all static definitions in function scope. + const DeclContext *DC = Def->getDeclContext(); + if (DC->getDeclKind() != Decl::Namespace) + return; + + auto Diag = + diag(Def->getLocation(), "%0 is a static definition in " + "anonymous namespace; static is redundant here") + << Def; + Token Tok; + SourceLocation Loc = Def->getSourceRange().getBegin(); + while (Loc < Def->getSourceRange().getEnd() && + !Lexer::getRawToken(Loc, Tok, *Result.SourceManager, getLangOpts(), + true)) { + SourceRange TokenRange(Tok.getLocation(), Tok.getEndLoc()); + StringRef SourceText = + Lexer::getSourceText(CharSourceRange::getTokenRange(TokenRange), + *Result.SourceManager, getLangOpts()); + if (SourceText == "static") { + Diag << FixItHint::CreateRemoval(TokenRange); + break; + } + Loc = Tok.getEndLoc(); + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.h new file mode 100644 index 0000000000..c28087e07e --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/StaticDefinitionInAnonymousNamespaceCheck.h @@ -0,0 +1,31 @@ +//===--- StaticDefinitionInAnonymousNamespaceCheck.h - clang-tidy*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATIC_DEFINITION_IN_ANONYMOUS_NAMESPACE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATIC_DEFINITION_IN_ANONYMOUS_NAMESPACE_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Finds static function and variable definitions in anonymous namespace. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/static-definition-in-anonymous-namespace.html +class StaticDefinitionInAnonymousNamespaceCheck : public ClangTidyCheck { +public: + StaticDefinitionInAnonymousNamespaceCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATIC_DEFINITION_IN_ANONYMOUS_NAMESPACE_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/StringCompareCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/StringCompareCheck.cpp new file mode 100644 index 0000000000..3b5d89c8c6 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/StringCompareCheck.cpp @@ -0,0 +1,76 @@ +//===-- StringCompareCheck.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 "StringCompareCheck.h" +#include "../utils/FixItHintUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +static const StringRef CompareMessage = "do not use 'compare' to test equality " + "of strings; use the string equality " + "operator instead"; + +void StringCompareCheck::registerMatchers(MatchFinder *Finder) { + const auto StrCompare = cxxMemberCallExpr( + callee(cxxMethodDecl(hasName("compare"), + ofClass(classTemplateSpecializationDecl( + hasName("::std::basic_string"))))), + hasArgument(0, expr().bind("str2")), argumentCountIs(1), + callee(memberExpr().bind("str1"))); + + // First and second case: cast str.compare(str) to boolean. + Finder->addMatcher( + traverse(TK_AsIs, + implicitCastExpr(hasImplicitDestinationType(booleanType()), + has(StrCompare)) + .bind("match1")), + this); + + // Third and fourth case: str.compare(str) == 0 and str.compare(str) != 0. + Finder->addMatcher( + binaryOperator(hasAnyOperatorName("==", "!="), + hasOperands(StrCompare.bind("compare"), + integerLiteral(equals(0)).bind("zero"))) + .bind("match2"), + this); +} + +void StringCompareCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Matched = Result.Nodes.getNodeAs<Stmt>("match1")) { + diag(Matched->getBeginLoc(), CompareMessage); + return; + } + + if (const auto *Matched = Result.Nodes.getNodeAs<Stmt>("match2")) { + const ASTContext &Ctx = *Result.Context; + + if (const auto *Zero = Result.Nodes.getNodeAs<Stmt>("zero")) { + const auto *Str1 = Result.Nodes.getNodeAs<MemberExpr>("str1"); + const auto *Str2 = Result.Nodes.getNodeAs<Stmt>("str2"); + const auto *Compare = Result.Nodes.getNodeAs<Stmt>("compare"); + + auto Diag = diag(Matched->getBeginLoc(), CompareMessage); + + if (Str1->isArrow()) + Diag << FixItHint::CreateInsertion(Str1->getBeginLoc(), "*"); + + Diag << tooling::fixit::createReplacement(*Zero, *Str2, Ctx) + << tooling::fixit::createReplacement(*Compare, *Str1->getBase(), + Ctx); + } + } + + // FIXME: Add fixit to fix the code for case one and two (match1). +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/StringCompareCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/StringCompareCheck.h new file mode 100644 index 0000000000..812736d806 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/StringCompareCheck.h @@ -0,0 +1,34 @@ +//===--- StringCompareCheck.h - clang-tidy-----------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STRINGCOMPARECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STRINGCOMPARECHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// This check flags all calls compare when used to check for string +/// equality or inequality. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/string-compare.html +class StringCompareCheck : public ClangTidyCheck { +public: + StringCompareCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STRINGCOMPARECHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp new file mode 100644 index 0000000000..7fe0c0982d --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp @@ -0,0 +1,808 @@ +//===--- SuspiciousCallArgumentCheck.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 "SuspiciousCallArgumentCheck.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include <optional> +#include <sstream> + +using namespace clang::ast_matchers; +namespace optutils = clang::tidy::utils::options; + +namespace clang::tidy::readability { + +namespace { +struct DefaultHeuristicConfiguration { + /// Whether the heuristic is to be enabled by default. + const bool Enabled; + + /// The upper bound of % of similarity the two strings might have to be + /// considered dissimilar. + /// (For purposes of configuration, -1 if the heuristic is not configurable + /// with bounds.) + const int8_t DissimilarBelow; + + /// The lower bound of % of similarity the two string must have to be + /// considered similar. + /// (For purposes of configuration, -1 if the heuristic is not configurable + /// with bounds.) + const int8_t SimilarAbove; + + /// Can the heuristic be configured with bounds? + bool hasBounds() const { return DissimilarBelow > -1 && SimilarAbove > -1; } +}; +} // namespace + +static constexpr std::size_t DefaultMinimumIdentifierNameLength = 3; + +static constexpr StringRef HeuristicToString[] = { + "Equality", "Abbreviation", "Prefix", "Suffix", + "Substring", "Levenshtein", "JaroWinkler", "Dice"}; + +static constexpr DefaultHeuristicConfiguration Defaults[] = { + {true, -1, -1}, // Equality. + {true, -1, -1}, // Abbreviation. + {true, 25, 30}, // Prefix. + {true, 25, 30}, // Suffix. + {true, 40, 50}, // Substring. + {true, 50, 66}, // Levenshtein. + {true, 75, 85}, // Jaro-Winkler. + {true, 60, 70}, // Dice. +}; + +static_assert( + sizeof(HeuristicToString) / sizeof(HeuristicToString[0]) == + SuspiciousCallArgumentCheck::HeuristicCount, + "Ensure that every heuristic has a corresponding stringified name"); +static_assert(sizeof(Defaults) / sizeof(Defaults[0]) == + SuspiciousCallArgumentCheck::HeuristicCount, + "Ensure that every heuristic has a default configuration."); + +namespace { +template <std::size_t I> struct HasWellConfiguredBounds { + static constexpr bool Value = + !((Defaults[I].DissimilarBelow == -1) ^ (Defaults[I].SimilarAbove == -1)); + static_assert(Value, "A heuristic must either have a dissimilarity and " + "similarity bound, or neither!"); +}; + +template <std::size_t I> struct HasWellConfiguredBoundsFold { + static constexpr bool Value = HasWellConfiguredBounds<I>::Value && + HasWellConfiguredBoundsFold<I - 1>::Value; +}; + +template <> struct HasWellConfiguredBoundsFold<0> { + static constexpr bool Value = HasWellConfiguredBounds<0>::Value; +}; + +struct AllHeuristicsBoundsWellConfigured { + static constexpr bool Value = + HasWellConfiguredBoundsFold<SuspiciousCallArgumentCheck::HeuristicCount - + 1>::Value; +}; + +static_assert(AllHeuristicsBoundsWellConfigured::Value); +} // namespace + +static constexpr llvm::StringLiteral DefaultAbbreviations = "addr=address;" + "arr=array;" + "attr=attribute;" + "buf=buffer;" + "cl=client;" + "cnt=count;" + "col=column;" + "cpy=copy;" + "dest=destination;" + "dist=distance" + "dst=distance;" + "elem=element;" + "hght=height;" + "i=index;" + "idx=index;" + "len=length;" + "ln=line;" + "lst=list;" + "nr=number;" + "num=number;" + "pos=position;" + "ptr=pointer;" + "ref=reference;" + "src=source;" + "srv=server;" + "stmt=statement;" + "str=string;" + "val=value;" + "var=variable;" + "vec=vector;" + "wdth=width"; + +static constexpr std::size_t SmallVectorSize = + SuspiciousCallArgumentCheck::SmallVectorSize; + +/// Returns how many % X is of Y. +static inline double percentage(double X, double Y) { return X / Y * 100.0; } + +static bool applyEqualityHeuristic(StringRef Arg, StringRef Param) { + return Arg.equals_insensitive(Param); +} + +static bool applyAbbreviationHeuristic( + const llvm::StringMap<std::string> &AbbreviationDictionary, StringRef Arg, + StringRef Param) { + if (AbbreviationDictionary.find(Arg) != AbbreviationDictionary.end() && + Param.equals(AbbreviationDictionary.lookup(Arg))) + return true; + + if (AbbreviationDictionary.find(Param) != AbbreviationDictionary.end() && + Arg.equals(AbbreviationDictionary.lookup(Param))) + return true; + + return false; +} + +/// Check whether the shorter String is a prefix of the longer String. +static bool applyPrefixHeuristic(StringRef Arg, StringRef Param, + int8_t Threshold) { + StringRef Shorter = Arg.size() < Param.size() ? Arg : Param; + StringRef Longer = Arg.size() >= Param.size() ? Arg : Param; + + if (Longer.startswith_insensitive(Shorter)) + return percentage(Shorter.size(), Longer.size()) > Threshold; + + return false; +} + +/// Check whether the shorter String is a suffix of the longer String. +static bool applySuffixHeuristic(StringRef Arg, StringRef Param, + int8_t Threshold) { + StringRef Shorter = Arg.size() < Param.size() ? Arg : Param; + StringRef Longer = Arg.size() >= Param.size() ? Arg : Param; + + if (Longer.endswith_insensitive(Shorter)) + return percentage(Shorter.size(), Longer.size()) > Threshold; + + return false; +} + +static bool applySubstringHeuristic(StringRef Arg, StringRef Param, + int8_t Threshold) { + + std::size_t MaxLength = 0; + SmallVector<std::size_t, SmallVectorSize> Current(Param.size()); + SmallVector<std::size_t, SmallVectorSize> Previous(Param.size()); + std::string ArgLower = Arg.lower(); + std::string ParamLower = Param.lower(); + + for (std::size_t I = 0; I < Arg.size(); ++I) { + for (std::size_t J = 0; J < Param.size(); ++J) { + if (ArgLower[I] == ParamLower[J]) { + if (I == 0 || J == 0) + Current[J] = 1; + else + Current[J] = 1 + Previous[J - 1]; + + MaxLength = std::max(MaxLength, Current[J]); + } else + Current[J] = 0; + } + + Current.swap(Previous); + } + + size_t LongerLength = std::max(Arg.size(), Param.size()); + return percentage(MaxLength, LongerLength) > Threshold; +} + +static bool applyLevenshteinHeuristic(StringRef Arg, StringRef Param, + int8_t Threshold) { + std::size_t LongerLength = std::max(Arg.size(), Param.size()); + double Dist = Arg.edit_distance(Param); + Dist = (1.0 - Dist / LongerLength) * 100.0; + return Dist > Threshold; +} + +// Based on http://en.wikipedia.org/wiki/Jaro–Winkler_distance. +static bool applyJaroWinklerHeuristic(StringRef Arg, StringRef Param, + int8_t Threshold) { + std::size_t Match = 0, Transpos = 0; + std::ptrdiff_t ArgLen = Arg.size(); + std::ptrdiff_t ParamLen = Param.size(); + SmallVector<int, SmallVectorSize> ArgFlags(ArgLen); + SmallVector<int, SmallVectorSize> ParamFlags(ParamLen); + std::ptrdiff_t Range = + std::max(std::ptrdiff_t{0}, std::max(ArgLen, ParamLen) / 2 - 1); + + // Calculate matching characters. + for (std::ptrdiff_t I = 0; I < ParamLen; ++I) + for (std::ptrdiff_t J = std::max(I - Range, std::ptrdiff_t{0}), + L = std::min(I + Range + 1, ArgLen); + J < L; ++J) + if (tolower(Param[I]) == tolower(Arg[J]) && !ArgFlags[J]) { + ArgFlags[J] = 1; + ParamFlags[I] = 1; + ++Match; + break; + } + + if (!Match) + return false; + + // Calculate character transpositions. + std::ptrdiff_t L = 0; + for (std::ptrdiff_t I = 0; I < ParamLen; ++I) { + if (ParamFlags[I] == 1) { + std::ptrdiff_t J; + for (J = L; J < ArgLen; ++J) + if (ArgFlags[J] == 1) { + L = J + 1; + break; + } + + if (tolower(Param[I]) != tolower(Arg[J])) + ++Transpos; + } + } + Transpos /= 2; + + // Jaro distance. + double MatchD = Match; + double Dist = ((MatchD / ArgLen) + (MatchD / ParamLen) + + ((MatchD - Transpos) / Match)) / + 3.0; + + // Calculate common string prefix up to 4 chars. + L = 0; + for (std::ptrdiff_t I = 0; + I < std::min(std::min(ArgLen, ParamLen), std::ptrdiff_t{4}); ++I) + if (tolower(Arg[I]) == tolower(Param[I])) + ++L; + + // Jaro-Winkler distance. + Dist = (Dist + (L * 0.1 * (1.0 - Dist))) * 100.0; + return Dist > Threshold; +} + +// Based on http://en.wikipedia.org/wiki/Sørensen–Dice_coefficient +static bool applyDiceHeuristic(StringRef Arg, StringRef Param, + int8_t Threshold) { + llvm::StringSet<> ArgBigrams; + llvm::StringSet<> ParamBigrams; + + // Extract character bigrams from Arg. + for (std::ptrdiff_t I = 0; I < static_cast<std::ptrdiff_t>(Arg.size()) - 1; + ++I) + ArgBigrams.insert(Arg.substr(I, 2).lower()); + + // Extract character bigrams from Param. + for (std::ptrdiff_t I = 0; I < static_cast<std::ptrdiff_t>(Param.size()) - 1; + ++I) + ParamBigrams.insert(Param.substr(I, 2).lower()); + + std::size_t Intersection = 0; + + // Find the intersection between the two sets. + for (auto IT = ParamBigrams.begin(); IT != ParamBigrams.end(); ++IT) + Intersection += ArgBigrams.count((IT->getKey())); + + // Calculate Dice coefficient. + return percentage(Intersection * 2.0, + ArgBigrams.size() + ParamBigrams.size()) > Threshold; +} + +/// Checks if ArgType binds to ParamType regarding reference-ness and +/// cv-qualifiers. +static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType) { + return !ParamType->isReferenceType() || + ParamType.getNonReferenceType().isAtLeastAsQualifiedAs( + ArgType.getNonReferenceType()); +} + +static bool isPointerOrArray(QualType TypeToCheck) { + return TypeToCheck->isPointerType() || TypeToCheck->isArrayType(); +} + +/// Checks whether ArgType is an array type identical to ParamType's array type. +/// Enforces array elements' qualifier compatibility as well. +static bool isCompatibleWithArrayReference(QualType ArgType, + QualType ParamType) { + if (!ArgType->isArrayType()) + return false; + // Here, qualifiers belong to the elements of the arrays. + if (!ParamType.isAtLeastAsQualifiedAs(ArgType)) + return false; + + return ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType(); +} + +static QualType convertToPointeeOrArrayElementQualType(QualType TypeToConvert) { + unsigned CVRqualifiers = 0; + // Save array element qualifiers, since getElementType() removes qualifiers + // from array elements. + if (TypeToConvert->isArrayType()) + CVRqualifiers = TypeToConvert.getLocalQualifiers().getCVRQualifiers(); + TypeToConvert = TypeToConvert->isPointerType() + ? TypeToConvert->getPointeeType() + : TypeToConvert->getAsArrayTypeUnsafe()->getElementType(); + TypeToConvert = TypeToConvert.withCVRQualifiers(CVRqualifiers); + return TypeToConvert; +} + +/// Checks if multilevel pointers' qualifiers compatibility continues on the +/// current pointer level. For multilevel pointers, C++ permits conversion, if +/// every cv-qualifier in ArgType also appears in the corresponding position in +/// ParamType, and if PramType has a cv-qualifier that's not in ArgType, then +/// every * in ParamType to the right of that cv-qualifier, except the last +/// one, must also be const-qualified. +static bool arePointersStillQualCompatible(QualType ArgType, QualType ParamType, + bool &IsParamContinuouslyConst) { + // The types are compatible, if the parameter is at least as qualified as the + // argument, and if it is more qualified, it has to be const on upper pointer + // levels. + bool AreTypesQualCompatible = + ParamType.isAtLeastAsQualifiedAs(ArgType) && + (!ParamType.hasQualifiers() || IsParamContinuouslyConst); + // Check whether the parameter's constness continues at the current pointer + // level. + IsParamContinuouslyConst &= ParamType.isConstQualified(); + + return AreTypesQualCompatible; +} + +/// Checks whether multilevel pointers are compatible in terms of levels, +/// qualifiers and pointee type. +static bool arePointerTypesCompatible(QualType ArgType, QualType ParamType, + bool IsParamContinuouslyConst) { + if (!arePointersStillQualCompatible(ArgType, ParamType, + IsParamContinuouslyConst)) + return false; + + do { + // Step down one pointer level. + ArgType = convertToPointeeOrArrayElementQualType(ArgType); + ParamType = convertToPointeeOrArrayElementQualType(ParamType); + + // Check whether cv-qualifiers permit compatibility on + // current level. + if (!arePointersStillQualCompatible(ArgType, ParamType, + IsParamContinuouslyConst)) + return false; + + if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType()) + return true; + + } while (ParamType->isPointerType() && ArgType->isPointerType()); + // The final type does not match, or pointer levels differ. + return false; +} + +/// Checks whether ArgType converts implicitly to ParamType. +static bool areTypesCompatible(QualType ArgType, QualType ParamType, + const ASTContext &Ctx) { + if (ArgType.isNull() || ParamType.isNull()) + return false; + + ArgType = ArgType.getCanonicalType(); + ParamType = ParamType.getCanonicalType(); + + if (ArgType == ParamType) + return true; + + // Check for constness and reference compatibility. + if (!areRefAndQualCompatible(ArgType, ParamType)) + return false; + + bool IsParamReference = ParamType->isReferenceType(); + + // Reference-ness has already been checked and should be removed + // before further checking. + ArgType = ArgType.getNonReferenceType(); + ParamType = ParamType.getNonReferenceType(); + + if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType()) + return true; + + // Arithmetic types are interconvertible, except scoped enums. + if (ParamType->isArithmeticType() && ArgType->isArithmeticType()) { + if ((ParamType->isEnumeralType() && + ParamType->castAs<EnumType>()->getDecl()->isScoped()) || + (ArgType->isEnumeralType() && + ArgType->castAs<EnumType>()->getDecl()->isScoped())) + return false; + + return true; + } + + // Check if the argument and the param are both function types (the parameter + // decayed to a function pointer). + if (ArgType->isFunctionType() && ParamType->isFunctionPointerType()) { + ParamType = ParamType->getPointeeType(); + return ArgType == ParamType; + } + + // Arrays or pointer arguments convert to array or pointer parameters. + if (!(isPointerOrArray(ArgType) && isPointerOrArray(ParamType))) + return false; + + // When ParamType is an array reference, ArgType has to be of the same-sized + // array-type with cv-compatible element type. + if (IsParamReference && ParamType->isArrayType()) + return isCompatibleWithArrayReference(ArgType, ParamType); + + bool IsParamContinuouslyConst = + !IsParamReference || ParamType.getNonReferenceType().isConstQualified(); + + // Remove the first level of indirection. + ArgType = convertToPointeeOrArrayElementQualType(ArgType); + ParamType = convertToPointeeOrArrayElementQualType(ParamType); + + // Check qualifier compatibility on the next level. + if (!ParamType.isAtLeastAsQualifiedAs(ArgType)) + return false; + + if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType()) + return true; + + // At this point, all possible C language implicit conversion were checked. + if (!Ctx.getLangOpts().CPlusPlus) + return false; + + // Check whether ParamType and ArgType were both pointers to a class or a + // struct, and check for inheritance. + if (ParamType->isStructureOrClassType() && + ArgType->isStructureOrClassType()) { + const auto *ArgDecl = ArgType->getAsCXXRecordDecl(); + const auto *ParamDecl = ParamType->getAsCXXRecordDecl(); + if (!ArgDecl || !ArgDecl->hasDefinition() || !ParamDecl || + !ParamDecl->hasDefinition()) + return false; + + return ArgDecl->isDerivedFrom(ParamDecl); + } + + // Unless argument and param are both multilevel pointers, the types are not + // convertible. + if (!(ParamType->isAnyPointerType() && ArgType->isAnyPointerType())) + return false; + + return arePointerTypesCompatible(ArgType, ParamType, + IsParamContinuouslyConst); +} + +static bool isOverloadedUnaryOrBinarySymbolOperator(const FunctionDecl *FD) { + switch (FD->getOverloadedOperator()) { + case OO_None: + case OO_Call: + case OO_Subscript: + case OO_New: + case OO_Delete: + case OO_Array_New: + case OO_Array_Delete: + case OO_Conditional: + case OO_Coawait: + return false; + + default: + return FD->getNumParams() <= 2; + } +} + +SuspiciousCallArgumentCheck::SuspiciousCallArgumentCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + MinimumIdentifierNameLength(Options.get( + "MinimumIdentifierNameLength", DefaultMinimumIdentifierNameLength)) { + auto GetToggleOpt = [this](Heuristic H) -> bool { + auto Idx = static_cast<std::size_t>(H); + assert(Idx < HeuristicCount); + return Options.get(HeuristicToString[Idx], Defaults[Idx].Enabled); + }; + auto GetBoundOpt = [this](Heuristic H, BoundKind BK) -> int8_t { + auto Idx = static_cast<std::size_t>(H); + assert(Idx < HeuristicCount); + + SmallString<32> Key = HeuristicToString[Idx]; + Key.append(BK == BoundKind::DissimilarBelow ? "DissimilarBelow" + : "SimilarAbove"); + int8_t Default = BK == BoundKind::DissimilarBelow + ? Defaults[Idx].DissimilarBelow + : Defaults[Idx].SimilarAbove; + return Options.get(Key, Default); + }; + for (std::size_t Idx = 0; Idx < HeuristicCount; ++Idx) { + auto H = static_cast<Heuristic>(Idx); + if (GetToggleOpt(H)) + AppliedHeuristics.emplace_back(H); + ConfiguredBounds.emplace_back( + std::make_pair(GetBoundOpt(H, BoundKind::DissimilarBelow), + GetBoundOpt(H, BoundKind::SimilarAbove))); + } + + for (StringRef Abbreviation : optutils::parseStringList( + Options.get("Abbreviations", DefaultAbbreviations))) { + auto KeyAndValue = Abbreviation.split("="); + assert(!KeyAndValue.first.empty() && !KeyAndValue.second.empty()); + AbbreviationDictionary.insert( + std::make_pair(KeyAndValue.first, KeyAndValue.second.str())); + } +} + +void SuspiciousCallArgumentCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "MinimumIdentifierNameLength", + MinimumIdentifierNameLength); + const auto &SetToggleOpt = [this, &Opts](Heuristic H) -> void { + auto Idx = static_cast<std::size_t>(H); + Options.store(Opts, HeuristicToString[Idx], isHeuristicEnabled(H)); + }; + const auto &SetBoundOpt = [this, &Opts](Heuristic H, BoundKind BK) -> void { + auto Idx = static_cast<std::size_t>(H); + assert(Idx < HeuristicCount); + if (!Defaults[Idx].hasBounds()) + return; + + SmallString<32> Key = HeuristicToString[Idx]; + Key.append(BK == BoundKind::DissimilarBelow ? "DissimilarBelow" + : "SimilarAbove"); + Options.store(Opts, Key, *getBound(H, BK)); + }; + + for (std::size_t Idx = 0; Idx < HeuristicCount; ++Idx) { + auto H = static_cast<Heuristic>(Idx); + SetToggleOpt(H); + SetBoundOpt(H, BoundKind::DissimilarBelow); + SetBoundOpt(H, BoundKind::SimilarAbove); + } + + SmallVector<std::string, 32> Abbreviations; + for (const auto &Abbreviation : AbbreviationDictionary) { + SmallString<32> EqualSignJoined; + EqualSignJoined.append(Abbreviation.first()); + EqualSignJoined.append("="); + EqualSignJoined.append(Abbreviation.second); + + if (!Abbreviation.second.empty()) + Abbreviations.emplace_back(EqualSignJoined.str()); + } + Options.store(Opts, "Abbreviations", + optutils::serializeStringList(std::vector<StringRef>( + Abbreviations.begin(), Abbreviations.end()))); +} + +bool SuspiciousCallArgumentCheck::isHeuristicEnabled(Heuristic H) const { + return llvm::is_contained(AppliedHeuristics, H); +} + +std::optional<int8_t> +SuspiciousCallArgumentCheck::getBound(Heuristic H, BoundKind BK) const { + auto Idx = static_cast<std::size_t>(H); + assert(Idx < HeuristicCount); + + if (!Defaults[Idx].hasBounds()) + return std::nullopt; + + switch (BK) { + case BoundKind::DissimilarBelow: + return ConfiguredBounds[Idx].first; + case BoundKind::SimilarAbove: + return ConfiguredBounds[Idx].second; + } + llvm_unreachable("Unhandled Bound kind."); +} + +void SuspiciousCallArgumentCheck::registerMatchers(MatchFinder *Finder) { + // Only match calls with at least 2 arguments. + Finder->addMatcher( + functionDecl(forEachDescendant(callExpr(unless(anyOf(argumentCountIs(0), + argumentCountIs(1)))) + .bind("functionCall"))) + .bind("callingFunc"), + this); +} + +void SuspiciousCallArgumentCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *MatchedCallExpr = + Result.Nodes.getNodeAs<CallExpr>("functionCall"); + const auto *Caller = Result.Nodes.getNodeAs<FunctionDecl>("callingFunc"); + assert(MatchedCallExpr && Caller); + + const Decl *CalleeDecl = MatchedCallExpr->getCalleeDecl(); + if (!CalleeDecl) + return; + + const FunctionDecl *CalleeFuncDecl = CalleeDecl->getAsFunction(); + if (!CalleeFuncDecl) + return; + if (CalleeFuncDecl == Caller) + // Ignore recursive calls. + return; + if (isOverloadedUnaryOrBinarySymbolOperator(CalleeFuncDecl)) + return; + + // Get param attributes. + setParamNamesAndTypes(CalleeFuncDecl); + + if (ParamNames.empty()) + return; + + // Get Arg attributes. + std::size_t InitialArgIndex = 0; + + if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(CalleeFuncDecl)) { + if (MethodDecl->getParent()->isLambda()) + // Lambda functions' first Arg are the lambda object. + InitialArgIndex = 1; + else if (MethodDecl->getOverloadedOperator() == OO_Call) + // For custom operator()s, the first Arg is the called object. + InitialArgIndex = 1; + } + + setArgNamesAndTypes(MatchedCallExpr, InitialArgIndex); + + if (ArgNames.empty()) + return; + + std::size_t ParamCount = ParamNames.size(); + + // Check similarity. + for (std::size_t I = 0; I < ParamCount; ++I) { + for (std::size_t J = I + 1; J < ParamCount; ++J) { + // Do not check if param or arg names are short, or not convertible. + if (!areParamAndArgComparable(I, J, *Result.Context)) + continue; + if (!areArgsSwapped(I, J)) + continue; + + // Warning at the call itself. + diag(MatchedCallExpr->getExprLoc(), + "%ordinal0 argument '%1' (passed to '%2') looks like it might be " + "swapped with the %ordinal3, '%4' (passed to '%5')") + << static_cast<unsigned>(I + 1) << ArgNames[I] << ParamNames[I] + << static_cast<unsigned>(J + 1) << ArgNames[J] << ParamNames[J] + << MatchedCallExpr->getArg(I)->getSourceRange() + << MatchedCallExpr->getArg(J)->getSourceRange(); + + // Note at the functions declaration. + SourceLocation IParNameLoc = + CalleeFuncDecl->getParamDecl(I)->getLocation(); + SourceLocation JParNameLoc = + CalleeFuncDecl->getParamDecl(J)->getLocation(); + + diag(CalleeFuncDecl->getLocation(), "in the call to %0, declared here", + DiagnosticIDs::Note) + << CalleeFuncDecl + << CharSourceRange::getTokenRange(IParNameLoc, IParNameLoc) + << CharSourceRange::getTokenRange(JParNameLoc, JParNameLoc); + } + } +} + +void SuspiciousCallArgumentCheck::setParamNamesAndTypes( + const FunctionDecl *CalleeFuncDecl) { + // Reset vectors, and fill them with the currently checked function's + // parameters' data. + ParamNames.clear(); + ParamTypes.clear(); + + for (const ParmVarDecl *Param : CalleeFuncDecl->parameters()) { + ParamTypes.push_back(Param->getType()); + + if (IdentifierInfo *II = Param->getIdentifier()) + ParamNames.push_back(II->getName()); + else + ParamNames.push_back(StringRef()); + } +} + +void SuspiciousCallArgumentCheck::setArgNamesAndTypes( + const CallExpr *MatchedCallExpr, std::size_t InitialArgIndex) { + // Reset vectors, and fill them with the currently checked function's + // arguments' data. + ArgNames.clear(); + ArgTypes.clear(); + + for (std::size_t I = InitialArgIndex, J = MatchedCallExpr->getNumArgs(); + I < J; ++I) { + assert(ArgTypes.size() == I - InitialArgIndex && + ArgNames.size() == ArgTypes.size() && + "Every iteration must put an element into the vectors!"); + + if (const auto *ArgExpr = dyn_cast<DeclRefExpr>( + MatchedCallExpr->getArg(I)->IgnoreUnlessSpelledInSource())) { + if (const auto *Var = dyn_cast<VarDecl>(ArgExpr->getDecl())) { + ArgTypes.push_back(Var->getType()); + ArgNames.push_back(Var->getName()); + continue; + } + if (const auto *FCall = dyn_cast<FunctionDecl>(ArgExpr->getDecl())) { + if (FCall->getNameInfo().getName().isIdentifier()) { + ArgTypes.push_back(FCall->getType()); + ArgNames.push_back(FCall->getName()); + continue; + } + } + } + + ArgTypes.push_back(QualType()); + ArgNames.push_back(StringRef()); + } +} + +bool SuspiciousCallArgumentCheck::areParamAndArgComparable( + std::size_t Position1, std::size_t Position2, const ASTContext &Ctx) const { + if (Position1 >= ArgNames.size() || Position2 >= ArgNames.size()) + return false; + + // Do not report for too short strings. + if (ArgNames[Position1].size() < MinimumIdentifierNameLength || + ArgNames[Position2].size() < MinimumIdentifierNameLength || + ParamNames[Position1].size() < MinimumIdentifierNameLength || + ParamNames[Position2].size() < MinimumIdentifierNameLength) + return false; + + if (!areTypesCompatible(ArgTypes[Position1], ParamTypes[Position2], Ctx) || + !areTypesCompatible(ArgTypes[Position2], ParamTypes[Position1], Ctx)) + return false; + + return true; +} + +bool SuspiciousCallArgumentCheck::areArgsSwapped(std::size_t Position1, + std::size_t Position2) const { + for (Heuristic H : AppliedHeuristics) { + bool A1ToP2Similar = areNamesSimilar( + ArgNames[Position2], ParamNames[Position1], H, BoundKind::SimilarAbove); + bool A2ToP1Similar = areNamesSimilar( + ArgNames[Position1], ParamNames[Position2], H, BoundKind::SimilarAbove); + + bool A1ToP1Dissimilar = + !areNamesSimilar(ArgNames[Position1], ParamNames[Position1], H, + BoundKind::DissimilarBelow); + bool A2ToP2Dissimilar = + !areNamesSimilar(ArgNames[Position2], ParamNames[Position2], H, + BoundKind::DissimilarBelow); + + if ((A1ToP2Similar || A2ToP1Similar) && A1ToP1Dissimilar && + A2ToP2Dissimilar) + return true; + } + return false; +} + +bool SuspiciousCallArgumentCheck::areNamesSimilar(StringRef Arg, + StringRef Param, Heuristic H, + BoundKind BK) const { + int8_t Threshold = -1; + if (std::optional<int8_t> GotBound = getBound(H, BK)) + Threshold = *GotBound; + + switch (H) { + case Heuristic::Equality: + return applyEqualityHeuristic(Arg, Param); + case Heuristic::Abbreviation: + return applyAbbreviationHeuristic(AbbreviationDictionary, Arg, Param); + case Heuristic::Prefix: + return applyPrefixHeuristic(Arg, Param, Threshold); + case Heuristic::Suffix: + return applySuffixHeuristic(Arg, Param, Threshold); + case Heuristic::Substring: + return applySubstringHeuristic(Arg, Param, Threshold); + case Heuristic::Levenshtein: + return applyLevenshteinHeuristic(Arg, Param, Threshold); + case Heuristic::JaroWinkler: + return applyJaroWinklerHeuristic(Arg, Param, Threshold); + case Heuristic::Dice: + return applyDiceHeuristic(Arg, Param, Threshold); + } + llvm_unreachable("Unhandled heuristic kind"); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/SuspiciousCallArgumentCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/SuspiciousCallArgumentCheck.h new file mode 100644 index 0000000000..38477d0800 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/SuspiciousCallArgumentCheck.h @@ -0,0 +1,97 @@ +//===--- SuspiciousCallArgumentCheck.h - clang-tidy -------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SUSPICIOUSCALLARGUMENTCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SUSPICIOUSCALLARGUMENTCHECK_H + +#include "../ClangTidyCheck.h" +#include "llvm/ADT/StringSet.h" +#include <optional> + +namespace clang::tidy::readability { + +/// Finds function calls where the arguments passed are provided out of order, +/// based on the difference between the argument name and the parameter names +/// of the function. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/suspicious-call-argument.html +class SuspiciousCallArgumentCheck : public ClangTidyCheck { + enum class Heuristic { + Equality, + Abbreviation, + Prefix, + Suffix, + Substring, + Levenshtein, + JaroWinkler, + Dice + }; + + /// When applying a heuristic, the value of this enum decides which kind of + /// bound will be selected from the bounds configured for the heuristic. + /// This only applies to heuristics that can take bounds. + enum class BoundKind { + /// Check for dissimilarity of the names. Names are deemed dissimilar if + /// the similarity measurement is **below** the configured threshold. + DissimilarBelow, + + /// Check for similarity of the names. Names are deemed similar if the + /// similarity measurement (the result of heuristic) is **above** the + /// configured threshold. + SimilarAbove + }; + +public: + static constexpr std::size_t SmallVectorSize = 8; + static constexpr std::size_t HeuristicCount = + static_cast<std::size_t>(Heuristic::Dice) + 1; + + SuspiciousCallArgumentCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const std::size_t MinimumIdentifierNameLength; + + /// The configuration for which heuristics were enabled. + SmallVector<Heuristic, HeuristicCount> AppliedHeuristics; + + /// The lower and upper bounds for each heuristic, as configured by the user. + SmallVector<std::pair<int8_t, int8_t>, HeuristicCount> ConfiguredBounds; + + /// The abbreviation-to-abbreviated map for the Abbreviation heuristic. + llvm::StringMap<std::string> AbbreviationDictionary; + + bool isHeuristicEnabled(Heuristic H) const; + std::optional<int8_t> getBound(Heuristic H, BoundKind BK) const; + + // Runtime information of the currently analyzed function call. + SmallVector<QualType, SmallVectorSize> ArgTypes; + SmallVector<StringRef, SmallVectorSize> ArgNames; + SmallVector<QualType, SmallVectorSize> ParamTypes; + SmallVector<StringRef, SmallVectorSize> ParamNames; + + void setParamNamesAndTypes(const FunctionDecl *CalleeFuncDecl); + + void setArgNamesAndTypes(const CallExpr *MatchedCallExpr, + std::size_t InitialArgIndex); + + bool areParamAndArgComparable(std::size_t Position1, std::size_t Position2, + const ASTContext &Ctx) const; + + bool areArgsSwapped(std::size_t Position1, std::size_t Position2) const; + + bool areNamesSimilar(StringRef Arg, StringRef Param, Heuristic H, + BoundKind BK) const; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_SUSPICIOUSCALLARGUMENTCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp new file mode 100644 index 0000000000..462085b023 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.cpp @@ -0,0 +1,82 @@ +//===--- UniqueptrDeleteReleaseCheck.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 "UniqueptrDeleteReleaseCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +void UniqueptrDeleteReleaseCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "PreferResetCall", PreferResetCall); +} + +UniqueptrDeleteReleaseCheck::UniqueptrDeleteReleaseCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + PreferResetCall(Options.get("PreferResetCall", false)) {} + +void UniqueptrDeleteReleaseCheck::registerMatchers(MatchFinder *Finder) { + + auto UniquePtrWithDefaultDelete = classTemplateSpecializationDecl( + hasName("::std::unique_ptr"), + hasTemplateArgument(1, refersToType(hasDeclaration(cxxRecordDecl( + hasName("::std::default_delete")))))); + + Finder->addMatcher( + cxxDeleteExpr( + unless(isInTemplateInstantiation()), + has(cxxMemberCallExpr( + callee(memberExpr(hasObjectExpression(anyOf( + hasType(UniquePtrWithDefaultDelete), + hasType(pointsTo( + UniquePtrWithDefaultDelete)))), + member(cxxMethodDecl(hasName("release")))) + .bind("release_expr"))) + .bind("release_call"))) + .bind("delete"), + this); +} + +void UniqueptrDeleteReleaseCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *DeleteExpr = Result.Nodes.getNodeAs<CXXDeleteExpr>("delete"); + const auto *ReleaseExpr = Result.Nodes.getNodeAs<MemberExpr>("release_expr"); + const auto *ReleaseCallExpr = + Result.Nodes.getNodeAs<CXXMemberCallExpr>("release_call"); + + if (ReleaseExpr->getBeginLoc().isMacroID()) + return; + + auto D = + diag(DeleteExpr->getBeginLoc(), "prefer '%select{= nullptr|reset()}0' " + "to reset 'unique_ptr<>' objects"); + D << PreferResetCall << DeleteExpr->getSourceRange() + << FixItHint::CreateRemoval(CharSourceRange::getCharRange( + DeleteExpr->getBeginLoc(), + DeleteExpr->getArgument()->getBeginLoc())); + if (PreferResetCall) { + D << FixItHint::CreateReplacement(ReleaseExpr->getMemberLoc(), "reset"); + } else { + if (ReleaseExpr->isArrow()) + D << FixItHint::CreateInsertion(ReleaseExpr->getBase()->getBeginLoc(), + "*"); + D << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ReleaseExpr->getOperatorLoc(), + ReleaseCallExpr->getEndLoc()), + " = nullptr"); + } +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h new file mode 100644 index 0000000000..4dfcf36e47 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/UniqueptrDeleteReleaseCheck.h @@ -0,0 +1,37 @@ +//===--- UniqueptrDeleteReleaseCheck.h - clang-tidy--------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNIQUEPTR_DELETE_RELEASE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNIQUEPTR_DELETE_RELEASE_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Flags statements of the form ``delete <unique_ptr expr>.release();`` and +/// replaces them with: ``<unique_ptr expr> = nullptr;`` +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/uniqueptr-delete-release.html +class UniqueptrDeleteReleaseCheck : public ClangTidyCheck { +public: + UniqueptrDeleteReleaseCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + const bool PreferResetCall; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNIQUEPTR_DELETE_RELEASE_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/UppercaseLiteralSuffixCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/UppercaseLiteralSuffixCheck.cpp new file mode 100644 index 0000000000..549331c2d9 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/UppercaseLiteralSuffixCheck.cpp @@ -0,0 +1,242 @@ +//===--- UppercaseLiteralSuffixCheck.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 "UppercaseLiteralSuffixCheck.h" +#include "../utils/ASTUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallString.h" +#include <cctype> +#include <optional> + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +namespace { + +struct IntegerLiteralCheck { + using type = clang::IntegerLiteral; + static constexpr llvm::StringLiteral Name = llvm::StringLiteral("integer"); + // What should be skipped before looking for the Suffixes? (Nothing here.) + static constexpr llvm::StringLiteral SkipFirst = llvm::StringLiteral(""); + // Suffix can only consist of 'u' and 'l' chars, and can be a complex number + // ('i', 'j'). In MS compatibility mode, suffixes like i32 are supported. + static constexpr llvm::StringLiteral Suffixes = + llvm::StringLiteral("uUlLiIjJ"); +}; +constexpr llvm::StringLiteral IntegerLiteralCheck::Name; +constexpr llvm::StringLiteral IntegerLiteralCheck::SkipFirst; +constexpr llvm::StringLiteral IntegerLiteralCheck::Suffixes; + +struct FloatingLiteralCheck { + using type = clang::FloatingLiteral; + static constexpr llvm::StringLiteral Name = + llvm::StringLiteral("floating point"); + // C++17 introduced hexadecimal floating-point literals, and 'f' is both a + // valid hexadecimal digit in a hex float literal and a valid floating-point + // literal suffix. + // So we can't just "skip to the chars that can be in the suffix". + // Since the exponent ('p'/'P') is mandatory for hexadecimal floating-point + // literals, we first skip everything before the exponent. + static constexpr llvm::StringLiteral SkipFirst = llvm::StringLiteral("pP"); + // Suffix can only consist of 'f', 'l', "f16", 'h', 'q' chars, + // and can be a complex number ('i', 'j'). + static constexpr llvm::StringLiteral Suffixes = + llvm::StringLiteral("fFlLhHqQiIjJ"); +}; +constexpr llvm::StringLiteral FloatingLiteralCheck::Name; +constexpr llvm::StringLiteral FloatingLiteralCheck::SkipFirst; +constexpr llvm::StringLiteral FloatingLiteralCheck::Suffixes; + +struct NewSuffix { + SourceRange LiteralLocation; + StringRef OldSuffix; + std::optional<FixItHint> FixIt; +}; + +std::optional<SourceLocation> getMacroAwareLocation(SourceLocation Loc, + const SourceManager &SM) { + // Do nothing if the provided location is invalid. + if (Loc.isInvalid()) + return std::nullopt; + // Look where the location was *actually* written. + SourceLocation SpellingLoc = SM.getSpellingLoc(Loc); + if (SpellingLoc.isInvalid()) + return std::nullopt; + return SpellingLoc; +} + +std::optional<SourceRange> getMacroAwareSourceRange(SourceRange Loc, + const SourceManager &SM) { + std::optional<SourceLocation> Begin = + getMacroAwareLocation(Loc.getBegin(), SM); + std::optional<SourceLocation> End = getMacroAwareLocation(Loc.getEnd(), SM); + if (!Begin || !End) + return std::nullopt; + return SourceRange(*Begin, *End); +} + +std::optional<std::string> +getNewSuffix(llvm::StringRef OldSuffix, + const std::vector<StringRef> &NewSuffixes) { + // If there is no config, just uppercase the entirety of the suffix. + if (NewSuffixes.empty()) + return OldSuffix.upper(); + // Else, find matching suffix, case-*insensitive*ly. + auto NewSuffix = + llvm::find_if(NewSuffixes, [OldSuffix](StringRef PotentialNewSuffix) { + return OldSuffix.equals_insensitive(PotentialNewSuffix); + }); + // Have a match, return it. + if (NewSuffix != NewSuffixes.end()) + return NewSuffix->str(); + // Nope, I guess we have to keep it as-is. + return std::nullopt; +} + +template <typename LiteralType> +std::optional<NewSuffix> +shouldReplaceLiteralSuffix(const Expr &Literal, + const std::vector<StringRef> &NewSuffixes, + const SourceManager &SM, const LangOptions &LO) { + NewSuffix ReplacementDsc; + + const auto &L = cast<typename LiteralType::type>(Literal); + + // The naive location of the literal. Is always valid. + ReplacementDsc.LiteralLocation = L.getSourceRange(); + + // Was this literal fully spelled or is it a product of macro expansion? + bool RangeCanBeFixed = + utils::rangeCanBeFixed(ReplacementDsc.LiteralLocation, &SM); + + // The literal may have macro expansion, we need the final expanded src range. + std::optional<SourceRange> Range = + getMacroAwareSourceRange(ReplacementDsc.LiteralLocation, SM); + if (!Range) + return std::nullopt; + + if (RangeCanBeFixed) + ReplacementDsc.LiteralLocation = *Range; + // Else keep the naive literal location! + + // Get the whole literal from the source buffer. + bool Invalid; + const StringRef LiteralSourceText = Lexer::getSourceText( + CharSourceRange::getTokenRange(*Range), SM, LO, &Invalid); + assert(!Invalid && "Failed to retrieve the source text."); + + // Make sure the first character is actually a digit, instead of + // something else, like a non-type template parameter. + if (!std::isdigit(static_cast<unsigned char>(LiteralSourceText.front()))) + return std::nullopt; + + size_t Skip = 0; + + // Do we need to ignore something before actually looking for the suffix? + if (!LiteralType::SkipFirst.empty()) { + // E.g. we can't look for 'f' suffix in hexadecimal floating-point literals + // until after we skip to the exponent (which is mandatory there), + // because hex-digit-sequence may contain 'f'. + Skip = LiteralSourceText.find_first_of(LiteralType::SkipFirst); + // We could be in non-hexadecimal floating-point literal, with no exponent. + if (Skip == StringRef::npos) + Skip = 0; + } + + // Find the beginning of the suffix by looking for the first char that is + // one of these chars that can be in the suffix, potentially starting looking + // in the exponent, if we are skipping hex-digit-sequence. + Skip = LiteralSourceText.find_first_of(LiteralType::Suffixes, /*From=*/Skip); + + // We can't check whether the *Literal has any suffix or not without actually + // looking for the suffix. So it is totally possible that there is no suffix. + if (Skip == StringRef::npos) + return std::nullopt; + + // Move the cursor in the source range to the beginning of the suffix. + Range->setBegin(Range->getBegin().getLocWithOffset(Skip)); + // And in our textual representation too. + ReplacementDsc.OldSuffix = LiteralSourceText.drop_front(Skip); + assert(!ReplacementDsc.OldSuffix.empty() && + "We still should have some chars left."); + + // And get the replacement suffix. + std::optional<std::string> NewSuffix = + getNewSuffix(ReplacementDsc.OldSuffix, NewSuffixes); + if (!NewSuffix || ReplacementDsc.OldSuffix == *NewSuffix) + return std::nullopt; // The suffix was already the way it should be. + + if (RangeCanBeFixed) + ReplacementDsc.FixIt = FixItHint::CreateReplacement(*Range, *NewSuffix); + + return ReplacementDsc; +} + +} // namespace + +UppercaseLiteralSuffixCheck::UppercaseLiteralSuffixCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + NewSuffixes( + utils::options::parseStringList(Options.get("NewSuffixes", ""))), + IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)) {} + +void UppercaseLiteralSuffixCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "NewSuffixes", + utils::options::serializeStringList(NewSuffixes)); + Options.store(Opts, "IgnoreMacros", IgnoreMacros); +} + +void UppercaseLiteralSuffixCheck::registerMatchers(MatchFinder *Finder) { + // Sadly, we can't check whether the literal has suffix or not. + // E.g. i32 suffix still results in 'BuiltinType::Kind::Int'. + // And such an info is not stored in the *Literal itself. + Finder->addMatcher( + stmt(eachOf(integerLiteral().bind(IntegerLiteralCheck::Name), + floatLiteral().bind(FloatingLiteralCheck::Name)), + unless(anyOf(hasParent(userDefinedLiteral()), + hasAncestor(substNonTypeTemplateParmExpr())))), + this); +} + +template <typename LiteralType> +bool UppercaseLiteralSuffixCheck::checkBoundMatch( + const MatchFinder::MatchResult &Result) { + const auto *Literal = + Result.Nodes.getNodeAs<typename LiteralType::type>(LiteralType::Name); + if (!Literal) + return false; + + // We won't *always* want to diagnose. + // We might have a suffix that is already uppercase. + if (auto Details = shouldReplaceLiteralSuffix<LiteralType>( + *Literal, NewSuffixes, *Result.SourceManager, getLangOpts())) { + if (Details->LiteralLocation.getBegin().isMacroID() && IgnoreMacros) + return true; + auto Complaint = diag(Details->LiteralLocation.getBegin(), + "%0 literal has suffix '%1', which is not uppercase") + << LiteralType::Name << Details->OldSuffix; + if (Details->FixIt) // Similarly, a fix-it is not always possible. + Complaint << *(Details->FixIt); + } + + return true; +} + +void UppercaseLiteralSuffixCheck::check( + const MatchFinder::MatchResult &Result) { + if (checkBoundMatch<IntegerLiteralCheck>(Result)) + return; // If it *was* IntegerLiteral, don't check for FloatingLiteral. + checkBoundMatch<FloatingLiteralCheck>(Result); +} + +} // namespace clang::tidy::readability diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/UppercaseLiteralSuffixCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/UppercaseLiteralSuffixCheck.h new file mode 100644 index 0000000000..a8af08f5a8 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/UppercaseLiteralSuffixCheck.h @@ -0,0 +1,43 @@ +//===--- UppercaseLiteralSuffixCheck.h - clang-tidy -------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UPPERCASELITERALSUFFIXCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UPPERCASELITERALSUFFIXCHECK_H + +#include "../ClangTidyCheck.h" +#include "../utils/OptionsUtils.h" + +namespace clang::tidy::readability { + +/// Detects when the integral literal or floating point literal has +/// non-uppercase suffix, and suggests to make the suffix uppercase. +/// Alternatively, a list of destination suffixes can be provided. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/uppercase-literal-suffix.html +class UppercaseLiteralSuffixCheck : public ClangTidyCheck { +public: + UppercaseLiteralSuffixCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + std::optional<TraversalKind> getCheckTraversalKind() const override { + return TK_IgnoreUnlessSpelledInSource; + } + +private: + template <typename LiteralType> + bool checkBoundMatch(const ast_matchers::MatchFinder::MatchResult &Result); + + const std::vector<StringRef> NewSuffixes; + const bool IgnoreMacros; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UPPERCASELITERALSUFFIXCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/UseAnyOfAllOfCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/UseAnyOfAllOfCheck.cpp new file mode 100644 index 0000000000..7cf0e0853f --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/UseAnyOfAllOfCheck.cpp @@ -0,0 +1,107 @@ +//===--- UseAnyOfAllOfCheck.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 "UseAnyOfAllOfCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/Analyses/ExprMutationAnalyzer.h" +#include "clang/Frontend/CompilerInstance.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace { +/// Matches a Stmt whose parent is a CompoundStmt, and which is directly +/// followed by a Stmt matching the inner matcher. +AST_MATCHER_P(Stmt, nextStmt, ast_matchers::internal::Matcher<Stmt>, + InnerMatcher) { + DynTypedNodeList Parents = Finder->getASTContext().getParents(Node); + if (Parents.size() != 1) + return false; + + auto *C = Parents[0].get<CompoundStmt>(); + if (!C) + return false; + + const auto *I = llvm::find(C->body(), &Node); + assert(I != C->body_end() && "C is parent of Node"); + if (++I == C->body_end()) + return false; // Node is last statement. + + return InnerMatcher.matches(**I, Finder, Builder); +} +} // namespace + +namespace tidy::readability { + +void UseAnyOfAllOfCheck::registerMatchers(MatchFinder *Finder) { + auto Returns = [](bool V) { + return returnStmt(hasReturnValue(cxxBoolLiteral(equals(V)))); + }; + + auto ReturnsButNotTrue = + returnStmt(hasReturnValue(unless(cxxBoolLiteral(equals(true))))); + auto ReturnsButNotFalse = + returnStmt(hasReturnValue(unless(cxxBoolLiteral(equals(false))))); + + Finder->addMatcher( + cxxForRangeStmt( + nextStmt(Returns(false).bind("final_return")), + hasBody(allOf(hasDescendant(Returns(true)), + unless(anyOf(hasDescendant(breakStmt()), + hasDescendant(gotoStmt()), + hasDescendant(ReturnsButNotTrue)))))) + .bind("any_of_loop"), + this); + + Finder->addMatcher( + cxxForRangeStmt( + nextStmt(Returns(true).bind("final_return")), + hasBody(allOf(hasDescendant(Returns(false)), + unless(anyOf(hasDescendant(breakStmt()), + hasDescendant(gotoStmt()), + hasDescendant(ReturnsButNotFalse)))))) + .bind("all_of_loop"), + this); +} + +static bool isViableLoop(const CXXForRangeStmt &S, ASTContext &Context) { + + ExprMutationAnalyzer Mutations(*S.getBody(), Context); + if (Mutations.isMutated(S.getLoopVariable())) + return false; + const auto Matches = + match(findAll(declRefExpr().bind("decl_ref")), *S.getBody(), Context); + + return llvm::none_of(Matches, [&Mutations](auto &DeclRef) { + // TODO: allow modifications of loop-local variables + return Mutations.isMutated( + DeclRef.template getNodeAs<DeclRefExpr>("decl_ref")->getDecl()); + }); +} + +void UseAnyOfAllOfCheck::check(const MatchFinder::MatchResult &Result) { + + if (const auto *S = Result.Nodes.getNodeAs<CXXForRangeStmt>("any_of_loop")) { + if (!isViableLoop(*S, *Result.Context)) + return; + + diag(S->getForLoc(), "replace loop by 'std%select{|::ranges}0::any_of()'") + << getLangOpts().CPlusPlus20; + } else if (const auto *S = + Result.Nodes.getNodeAs<CXXForRangeStmt>("all_of_loop")) { + if (!isViableLoop(*S, *Result.Context)) + return; + + diag(S->getForLoc(), "replace loop by 'std%select{|::ranges}0::all_of()'") + << getLangOpts().CPlusPlus20; + } +} + +} // namespace tidy::readability +} // namespace clang diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/UseAnyOfAllOfCheck.h b/contrib/libs/clang16/tools/extra/clang-tidy/readability/UseAnyOfAllOfCheck.h new file mode 100644 index 0000000000..4e53b3f5a8 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/UseAnyOfAllOfCheck.h @@ -0,0 +1,36 @@ +//===--- UseAnyOfAllOfCheck.h - clang-tidy-----------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USEALGORITHMCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USEALGORITHMCHECK_H + +#include "../ClangTidyCheck.h" +#include "../utils/IncludeInserter.h" + +namespace clang::tidy::readability { + +/// Finds ranged-based for loops that can be replaced by a call to std::any_of +/// or std::all_of. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/use-anyofallof.html +class UseAnyOfAllOfCheck : public ClangTidyCheck { +public: + using ClangTidyCheck::ClangTidyCheck; + + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus11; + } + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_USEALGORITHMCHECK_H diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/ya.make b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ya.make new file mode 100644 index 0000000000..59200fc0a9 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/ya.make @@ -0,0 +1,80 @@ +# Generated by devtools/yamaker. + +LIBRARY() + +LICENSE(Apache-2.0 WITH LLVM-exception) + +LICENSE_TEXTS(.yandex_meta/licenses.list.txt) + +PEERDIR( + contrib/libs/clang16 + contrib/libs/clang16/include + contrib/libs/clang16/lib + contrib/libs/clang16/lib/AST + contrib/libs/clang16/lib/ASTMatchers + contrib/libs/clang16/lib/Analysis + contrib/libs/clang16/lib/Basic + contrib/libs/clang16/lib/Lex + contrib/libs/clang16/lib/Tooling + contrib/libs/clang16/tools/extra/clang-tidy/utils + contrib/libs/llvm16 + contrib/libs/llvm16/lib/Frontend/OpenMP + contrib/libs/llvm16/lib/Support +) + +ADDINCL( + contrib/libs/clang16/tools/extra/clang-tidy/readability +) + +NO_COMPILER_WARNINGS() + +NO_UTIL() + +SRCS( + AvoidConstParamsInDecls.cpp + BracesAroundStatementsCheck.cpp + ConstReturnTypeCheck.cpp + ContainerContainsCheck.cpp + ContainerDataPointerCheck.cpp + ContainerSizeEmptyCheck.cpp + ConvertMemberFunctionsToStatic.cpp + DeleteNullPointerCheck.cpp + DuplicateIncludeCheck.cpp + ElseAfterReturnCheck.cpp + FunctionCognitiveComplexityCheck.cpp + FunctionSizeCheck.cpp + IdentifierLengthCheck.cpp + IdentifierNamingCheck.cpp + ImplicitBoolConversionCheck.cpp + InconsistentDeclarationParameterNameCheck.cpp + IsolateDeclarationCheck.cpp + MagicNumbersCheck.cpp + MakeMemberFunctionConstCheck.cpp + MisleadingIndentationCheck.cpp + MisplacedArrayIndexCheck.cpp + NamedParameterCheck.cpp + NamespaceCommentCheck.cpp + NonConstParameterCheck.cpp + QualifiedAutoCheck.cpp + ReadabilityTidyModule.cpp + RedundantAccessSpecifiersCheck.cpp + RedundantControlFlowCheck.cpp + RedundantDeclarationCheck.cpp + RedundantFunctionPtrDereferenceCheck.cpp + RedundantMemberInitCheck.cpp + RedundantPreprocessorCheck.cpp + RedundantSmartptrGetCheck.cpp + RedundantStringCStrCheck.cpp + RedundantStringInitCheck.cpp + SimplifyBooleanExprCheck.cpp + SimplifySubscriptExprCheck.cpp + StaticAccessedThroughInstanceCheck.cpp + StaticDefinitionInAnonymousNamespaceCheck.cpp + StringCompareCheck.cpp + SuspiciousCallArgumentCheck.cpp + UniqueptrDeleteReleaseCheck.cpp + UppercaseLiteralSuffixCheck.cpp + UseAnyOfAllOfCheck.cpp +) + +END() |