|  | //===--- UseScopedLockCheck.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 "UseScopedLockCheck.h" | 
|  | #include "clang/AST/ASTContext.h" | 
|  | #include "clang/AST/Decl.h" | 
|  | #include "clang/AST/Stmt.h" | 
|  | #include "clang/AST/Type.h" | 
|  | #include "clang/ASTMatchers/ASTMatchFinder.h" | 
|  | #include "clang/ASTMatchers/ASTMatchers.h" | 
|  | #include "clang/Basic/SourceLocation.h" | 
|  | #include "clang/Lex/Lexer.h" | 
|  | #include "llvm/ADT/SmallVector.h" | 
|  | #include "llvm/ADT/Twine.h" | 
|  |  | 
|  | using namespace clang::ast_matchers; | 
|  |  | 
|  | namespace clang::tidy::modernize { | 
|  |  | 
|  | static bool isLockGuardDecl(const NamedDecl *Decl) { | 
|  | return Decl->getDeclName().isIdentifier() && | 
|  | Decl->getName() == "lock_guard" && Decl->isInStdNamespace(); | 
|  | } | 
|  |  | 
|  | static bool isLockGuard(const QualType &Type) { | 
|  | if (const auto *Record = Type->getAsCanonical<RecordType>()) | 
|  | if (const RecordDecl *Decl = Record->getOriginalDecl()) | 
|  | return isLockGuardDecl(Decl); | 
|  |  | 
|  | if (const auto *TemplateSpecType = Type->getAs<TemplateSpecializationType>()) | 
|  | if (const TemplateDecl *Decl = | 
|  | TemplateSpecType->getTemplateName().getAsTemplateDecl()) | 
|  | return isLockGuardDecl(Decl); | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static llvm::SmallVector<const VarDecl *> | 
|  | getLockGuardsFromDecl(const DeclStmt *DS) { | 
|  | llvm::SmallVector<const VarDecl *> LockGuards; | 
|  |  | 
|  | for (const Decl *Decl : DS->decls()) { | 
|  | if (const auto *VD = dyn_cast<VarDecl>(Decl)) { | 
|  | const QualType Type = | 
|  | VD->getType().getCanonicalType().getUnqualifiedType(); | 
|  | if (isLockGuard(Type)) | 
|  | LockGuards.push_back(VD); | 
|  | } | 
|  | } | 
|  |  | 
|  | return LockGuards; | 
|  | } | 
|  |  | 
|  | // Scans through the statements in a block and groups consecutive | 
|  | // 'std::lock_guard' variable declarations together. | 
|  | static llvm::SmallVector<llvm::SmallVector<const VarDecl *>> | 
|  | findLocksInCompoundStmt(const CompoundStmt *Block, | 
|  | const ast_matchers::MatchFinder::MatchResult &Result) { | 
|  | // store groups of consecutive 'std::lock_guard' declarations | 
|  | llvm::SmallVector<llvm::SmallVector<const VarDecl *>> LockGuardGroups; | 
|  | llvm::SmallVector<const VarDecl *> CurrentLockGuardGroup; | 
|  |  | 
|  | auto AddAndClearCurrentGroup = [&]() { | 
|  | if (!CurrentLockGuardGroup.empty()) { | 
|  | LockGuardGroups.push_back(CurrentLockGuardGroup); | 
|  | CurrentLockGuardGroup.clear(); | 
|  | } | 
|  | }; | 
|  |  | 
|  | for (const Stmt *Stmt : Block->body()) { | 
|  | if (const auto *DS = dyn_cast<DeclStmt>(Stmt)) { | 
|  | llvm::SmallVector<const VarDecl *> LockGuards = getLockGuardsFromDecl(DS); | 
|  |  | 
|  | if (!LockGuards.empty()) { | 
|  | CurrentLockGuardGroup.append(LockGuards); | 
|  | continue; | 
|  | } | 
|  | } | 
|  | AddAndClearCurrentGroup(); | 
|  | } | 
|  |  | 
|  | AddAndClearCurrentGroup(); | 
|  |  | 
|  | return LockGuardGroups; | 
|  | } | 
|  |  | 
|  | // Find the exact source range of the 'lock_guard' token | 
|  | static SourceRange getLockGuardRange(const TypeSourceInfo *SourceInfo) { | 
|  | const TypeLoc LockGuardTypeLoc = SourceInfo->getTypeLoc(); | 
|  |  | 
|  | return {LockGuardTypeLoc.getBeginLoc(), LockGuardTypeLoc.getEndLoc()}; | 
|  | } | 
|  |  | 
|  | // Find the exact source range of the 'lock_guard' name token | 
|  | static SourceRange getLockGuardNameRange(const TypeSourceInfo *SourceInfo) { | 
|  | const auto TemplateLoc = | 
|  | SourceInfo->getTypeLoc().getAs<TemplateSpecializationTypeLoc>(); | 
|  | if (!TemplateLoc) | 
|  | return {}; | 
|  |  | 
|  | return {TemplateLoc.getTemplateNameLoc(), | 
|  | TemplateLoc.getLAngleLoc().getLocWithOffset(-1)}; | 
|  | } | 
|  |  | 
|  | const static StringRef UseScopedLockMessage = | 
|  | "use 'std::scoped_lock' instead of 'std::lock_guard'"; | 
|  |  | 
|  | UseScopedLockCheck::UseScopedLockCheck(StringRef Name, | 
|  | ClangTidyContext *Context) | 
|  | : ClangTidyCheck(Name, Context), | 
|  | WarnOnSingleLocks(Options.get("WarnOnSingleLocks", true)), | 
|  | WarnOnUsingAndTypedef(Options.get("WarnOnUsingAndTypedef", true)) {} | 
|  |  | 
|  | void UseScopedLockCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { | 
|  | Options.store(Opts, "WarnOnSingleLocks", WarnOnSingleLocks); | 
|  | Options.store(Opts, "WarnOnUsingAndTypedef", WarnOnUsingAndTypedef); | 
|  | } | 
|  |  | 
|  | void UseScopedLockCheck::registerMatchers(MatchFinder *Finder) { | 
|  | const auto LockGuardClassDecl = | 
|  | namedDecl(hasName("lock_guard"), isInStdNamespace()); | 
|  |  | 
|  | const auto LockGuardType = | 
|  | qualType(anyOf(hasUnqualifiedDesugaredType( | 
|  | recordType(hasDeclaration(LockGuardClassDecl))), | 
|  | hasUnqualifiedDesugaredType(templateSpecializationType( | 
|  | hasDeclaration(LockGuardClassDecl))))); | 
|  |  | 
|  | const auto LockVarDecl = varDecl(hasType(LockGuardType)); | 
|  |  | 
|  | if (WarnOnSingleLocks) { | 
|  | Finder->addMatcher( | 
|  | compoundStmt( | 
|  | unless(isExpansionInSystemHeader()), | 
|  | has(declStmt(has(LockVarDecl)).bind("lock-decl-single")), | 
|  | unless(has(declStmt(unless(equalsBoundNode("lock-decl-single")), | 
|  | has(LockVarDecl))))), | 
|  | this); | 
|  | } | 
|  |  | 
|  | Finder->addMatcher( | 
|  | compoundStmt(unless(isExpansionInSystemHeader()), | 
|  | has(declStmt(has(LockVarDecl)).bind("lock-decl-multiple")), | 
|  | has(declStmt(unless(equalsBoundNode("lock-decl-multiple")), | 
|  | has(LockVarDecl)))) | 
|  | .bind("block-multiple"), | 
|  | this); | 
|  |  | 
|  | if (WarnOnUsingAndTypedef) { | 
|  | // Match 'typedef std::lock_guard<std::mutex> Lock' | 
|  | Finder->addMatcher(typedefDecl(unless(isExpansionInSystemHeader()), | 
|  | hasType(hasUnderlyingType(LockGuardType))) | 
|  | .bind("lock-guard-typedef"), | 
|  | this); | 
|  |  | 
|  | // Match 'using Lock = std::lock_guard<std::mutex>' | 
|  | Finder->addMatcher(typeAliasDecl(unless(isExpansionInSystemHeader()), | 
|  | hasType(templateSpecializationType( | 
|  | hasDeclaration(LockGuardClassDecl)))) | 
|  | .bind("lock-guard-using-alias"), | 
|  | this); | 
|  |  | 
|  | // Match 'using std::lock_guard' | 
|  | Finder->addMatcher( | 
|  | usingDecl(unless(isExpansionInSystemHeader()), | 
|  | hasAnyUsingShadowDecl(hasTargetDecl(LockGuardClassDecl))) | 
|  | .bind("lock-guard-using-decl"), | 
|  | this); | 
|  | } | 
|  | } | 
|  |  | 
|  | void UseScopedLockCheck::check(const MatchFinder::MatchResult &Result) { | 
|  | if (const auto *DS = Result.Nodes.getNodeAs<DeclStmt>("lock-decl-single")) { | 
|  | llvm::SmallVector<const VarDecl *> Decls = getLockGuardsFromDecl(DS); | 
|  | diagOnMultipleLocks({Decls}, Result); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (const auto *Compound = | 
|  | Result.Nodes.getNodeAs<CompoundStmt>("block-multiple")) { | 
|  | diagOnMultipleLocks(findLocksInCompoundStmt(Compound, Result), Result); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (const auto *Typedef = | 
|  | Result.Nodes.getNodeAs<TypedefDecl>("lock-guard-typedef")) { | 
|  | diagOnSourceInfo(Typedef->getTypeSourceInfo(), Result); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (const auto *UsingAlias = | 
|  | Result.Nodes.getNodeAs<TypeAliasDecl>("lock-guard-using-alias")) { | 
|  | diagOnSourceInfo(UsingAlias->getTypeSourceInfo(), Result); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (const auto *Using = | 
|  | Result.Nodes.getNodeAs<UsingDecl>("lock-guard-using-decl")) { | 
|  | diagOnUsingDecl(Using, Result); | 
|  | } | 
|  | } | 
|  |  | 
|  | void UseScopedLockCheck::diagOnSingleLock( | 
|  | const VarDecl *LockGuard, const MatchFinder::MatchResult &Result) { | 
|  | auto Diag = diag(LockGuard->getBeginLoc(), UseScopedLockMessage); | 
|  |  | 
|  | const SourceRange LockGuardTypeRange = | 
|  | getLockGuardRange(LockGuard->getTypeSourceInfo()); | 
|  |  | 
|  | if (LockGuardTypeRange.isInvalid()) | 
|  | return; | 
|  |  | 
|  | // Create Fix-its only if we can find the constructor call to properly handle | 
|  | // 'std::lock_guard l(m, std::adopt_lock)' case. | 
|  | const auto *CtorCall = dyn_cast<CXXConstructExpr>(LockGuard->getInit()); | 
|  | if (!CtorCall) | 
|  | return; | 
|  |  | 
|  | if (CtorCall->getNumArgs() == 1) { | 
|  | Diag << FixItHint::CreateReplacement(LockGuardTypeRange, | 
|  | "std::scoped_lock"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (CtorCall->getNumArgs() == 2) { | 
|  | const Expr *const *CtorArgs = CtorCall->getArgs(); | 
|  |  | 
|  | const Expr *MutexArg = CtorArgs[0]; | 
|  | const Expr *AdoptLockArg = CtorArgs[1]; | 
|  |  | 
|  | const StringRef MutexSourceText = Lexer::getSourceText( | 
|  | CharSourceRange::getTokenRange(MutexArg->getSourceRange()), | 
|  | *Result.SourceManager, Result.Context->getLangOpts()); | 
|  | const StringRef AdoptLockSourceText = Lexer::getSourceText( | 
|  | CharSourceRange::getTokenRange(AdoptLockArg->getSourceRange()), | 
|  | *Result.SourceManager, Result.Context->getLangOpts()); | 
|  |  | 
|  | Diag << FixItHint::CreateReplacement(LockGuardTypeRange, "std::scoped_lock") | 
|  | << FixItHint::CreateReplacement( | 
|  | SourceRange(MutexArg->getBeginLoc(), AdoptLockArg->getEndLoc()), | 
|  | (llvm::Twine(AdoptLockSourceText) + ", " + MutexSourceText) | 
|  | .str()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | llvm_unreachable("Invalid argument number of std::lock_guard constructor"); | 
|  | } | 
|  |  | 
|  | void UseScopedLockCheck::diagOnMultipleLocks( | 
|  | const llvm::SmallVector<llvm::SmallVector<const VarDecl *>> &LockGroups, | 
|  | const ast_matchers::MatchFinder::MatchResult &Result) { | 
|  | for (const llvm::SmallVector<const VarDecl *> &Group : LockGroups) { | 
|  | if (Group.size() == 1) { | 
|  | if (WarnOnSingleLocks) | 
|  | diagOnSingleLock(Group[0], Result); | 
|  | } else { | 
|  | diag(Group[0]->getBeginLoc(), | 
|  | "use single 'std::scoped_lock' instead of multiple " | 
|  | "'std::lock_guard'"); | 
|  |  | 
|  | for (const VarDecl *Lock : llvm::drop_begin(Group)) | 
|  | diag(Lock->getLocation(), "additional 'std::lock_guard' declared here", | 
|  | DiagnosticIDs::Note); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void UseScopedLockCheck::diagOnSourceInfo( | 
|  | const TypeSourceInfo *LockGuardSourceInfo, | 
|  | const ast_matchers::MatchFinder::MatchResult &Result) { | 
|  | const TypeLoc TL = LockGuardSourceInfo->getTypeLoc(); | 
|  |  | 
|  | if (const auto TTL = TL.getAs<TemplateSpecializationTypeLoc>()) { | 
|  | auto Diag = diag(TTL.getBeginLoc(), UseScopedLockMessage); | 
|  |  | 
|  | const SourceRange LockGuardRange = | 
|  | getLockGuardNameRange(LockGuardSourceInfo); | 
|  | if (LockGuardRange.isInvalid()) | 
|  | return; | 
|  |  | 
|  | Diag << FixItHint::CreateReplacement(LockGuardRange, "scoped_lock"); | 
|  | } | 
|  | } | 
|  |  | 
|  | void UseScopedLockCheck::diagOnUsingDecl( | 
|  | const UsingDecl *UsingDecl, | 
|  | const ast_matchers::MatchFinder::MatchResult &Result) { | 
|  | diag(UsingDecl->getLocation(), UseScopedLockMessage) | 
|  | << FixItHint::CreateReplacement(UsingDecl->getLocation(), "scoped_lock"); | 
|  | } | 
|  |  | 
|  | } // namespace clang::tidy::modernize |