clang-tools 22.0.0git
UseScopedLockCheck.cpp
Go to the documentation of this file.
1//===--- UseScopedLockCheck.cpp - clang-tidy ------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "clang/AST/ASTContext.h"
11#include "clang/AST/Decl.h"
12#include "clang/AST/Stmt.h"
13#include "clang/AST/Type.h"
14#include "clang/ASTMatchers/ASTMatchFinder.h"
15#include "clang/ASTMatchers/ASTMatchers.h"
16#include "clang/Basic/SourceLocation.h"
17#include "clang/Lex/Lexer.h"
18#include "llvm/ADT/SmallVector.h"
19#include "llvm/ADT/Twine.h"
20
21using namespace clang::ast_matchers;
22
23namespace clang::tidy::modernize {
24
25static bool isLockGuardDecl(const NamedDecl *Decl) {
26 return Decl->getDeclName().isIdentifier() &&
27 Decl->getName() == "lock_guard" && Decl->isInStdNamespace();
28}
29
30static bool isLockGuard(const QualType &Type) {
31 if (const auto *Record = Type->getAsCanonical<RecordType>())
32 if (const RecordDecl *Decl = Record->getOriginalDecl())
33 return isLockGuardDecl(Decl);
34
35 if (const auto *TemplateSpecType = Type->getAs<TemplateSpecializationType>())
36 if (const TemplateDecl *Decl =
37 TemplateSpecType->getTemplateName().getAsTemplateDecl())
38 return isLockGuardDecl(Decl);
39
40 return false;
41}
42
43static llvm::SmallVector<const VarDecl *>
44getLockGuardsFromDecl(const DeclStmt *DS) {
45 llvm::SmallVector<const VarDecl *> LockGuards;
46
47 for (const Decl *Decl : DS->decls()) {
48 if (const auto *VD = dyn_cast<VarDecl>(Decl)) {
49 const QualType Type =
50 VD->getType().getCanonicalType().getUnqualifiedType();
51 if (isLockGuard(Type))
52 LockGuards.push_back(VD);
53 }
54 }
55
56 return LockGuards;
57}
58
59// Scans through the statements in a block and groups consecutive
60// 'std::lock_guard' variable declarations together.
61static llvm::SmallVector<llvm::SmallVector<const VarDecl *>>
62findLocksInCompoundStmt(const CompoundStmt *Block,
63 const ast_matchers::MatchFinder::MatchResult &Result) {
64 // store groups of consecutive 'std::lock_guard' declarations
65 llvm::SmallVector<llvm::SmallVector<const VarDecl *>> LockGuardGroups;
66 llvm::SmallVector<const VarDecl *> CurrentLockGuardGroup;
67
68 auto AddAndClearCurrentGroup = [&]() {
69 if (!CurrentLockGuardGroup.empty()) {
70 LockGuardGroups.push_back(CurrentLockGuardGroup);
71 CurrentLockGuardGroup.clear();
72 }
73 };
74
75 for (const Stmt *Stmt : Block->body()) {
76 if (const auto *DS = dyn_cast<DeclStmt>(Stmt)) {
77 llvm::SmallVector<const VarDecl *> LockGuards = getLockGuardsFromDecl(DS);
78
79 if (!LockGuards.empty()) {
80 CurrentLockGuardGroup.append(LockGuards);
81 continue;
82 }
83 }
84 AddAndClearCurrentGroup();
85 }
86
87 AddAndClearCurrentGroup();
88
89 return LockGuardGroups;
90}
91
92// Find the exact source range of the 'lock_guard' token
93static SourceRange getLockGuardRange(const TypeSourceInfo *SourceInfo) {
94 const TypeLoc LockGuardTypeLoc = SourceInfo->getTypeLoc();
95
96 return {LockGuardTypeLoc.getBeginLoc(), LockGuardTypeLoc.getEndLoc()};
97}
98
99// Find the exact source range of the 'lock_guard' name token
100static SourceRange getLockGuardNameRange(const TypeSourceInfo *SourceInfo) {
101 const auto TemplateLoc =
102 SourceInfo->getTypeLoc().getAs<TemplateSpecializationTypeLoc>();
103 if (!TemplateLoc)
104 return {};
105
106 return {TemplateLoc.getTemplateNameLoc(),
107 TemplateLoc.getLAngleLoc().getLocWithOffset(-1)};
108}
109
110const static StringRef UseScopedLockMessage =
111 "use 'std::scoped_lock' instead of 'std::lock_guard'";
112
114 ClangTidyContext *Context)
115 : ClangTidyCheck(Name, Context),
116 WarnOnSingleLocks(Options.get("WarnOnSingleLocks", true)),
117 WarnOnUsingAndTypedef(Options.get("WarnOnUsingAndTypedef", true)) {}
118
120 Options.store(Opts, "WarnOnSingleLocks", WarnOnSingleLocks);
121 Options.store(Opts, "WarnOnUsingAndTypedef", WarnOnUsingAndTypedef);
122}
123
124void UseScopedLockCheck::registerMatchers(MatchFinder *Finder) {
125 const auto LockGuardClassDecl =
126 namedDecl(hasName("lock_guard"), isInStdNamespace());
127
128 const auto LockGuardType =
129 qualType(anyOf(hasUnqualifiedDesugaredType(
130 recordType(hasDeclaration(LockGuardClassDecl))),
131 hasUnqualifiedDesugaredType(templateSpecializationType(
132 hasDeclaration(LockGuardClassDecl)))));
133
134 const auto LockVarDecl = varDecl(hasType(LockGuardType));
135
136 if (WarnOnSingleLocks) {
137 Finder->addMatcher(
138 compoundStmt(
139 unless(isExpansionInSystemHeader()),
140 has(declStmt(has(LockVarDecl)).bind("lock-decl-single")),
141 unless(has(declStmt(unless(equalsBoundNode("lock-decl-single")),
142 has(LockVarDecl))))),
143 this);
144 }
145
146 Finder->addMatcher(
147 compoundStmt(unless(isExpansionInSystemHeader()),
148 has(declStmt(has(LockVarDecl)).bind("lock-decl-multiple")),
149 has(declStmt(unless(equalsBoundNode("lock-decl-multiple")),
150 has(LockVarDecl))))
151 .bind("block-multiple"),
152 this);
153
154 if (WarnOnUsingAndTypedef) {
155 // Match 'typedef std::lock_guard<std::mutex> Lock'
156 Finder->addMatcher(typedefDecl(unless(isExpansionInSystemHeader()),
157 hasType(hasUnderlyingType(LockGuardType)))
158 .bind("lock-guard-typedef"),
159 this);
160
161 // Match 'using Lock = std::lock_guard<std::mutex>'
162 Finder->addMatcher(typeAliasDecl(unless(isExpansionInSystemHeader()),
163 hasType(templateSpecializationType(
164 hasDeclaration(LockGuardClassDecl))))
165 .bind("lock-guard-using-alias"),
166 this);
167
168 // Match 'using std::lock_guard'
169 Finder->addMatcher(
170 usingDecl(unless(isExpansionInSystemHeader()),
171 hasAnyUsingShadowDecl(hasTargetDecl(LockGuardClassDecl)))
172 .bind("lock-guard-using-decl"),
173 this);
174 }
175}
176
177void UseScopedLockCheck::check(const MatchFinder::MatchResult &Result) {
178 if (const auto *DS = Result.Nodes.getNodeAs<DeclStmt>("lock-decl-single")) {
179 llvm::SmallVector<const VarDecl *> Decls = getLockGuardsFromDecl(DS);
180 diagOnMultipleLocks({Decls}, Result);
181 return;
182 }
183
184 if (const auto *Compound =
185 Result.Nodes.getNodeAs<CompoundStmt>("block-multiple")) {
186 diagOnMultipleLocks(findLocksInCompoundStmt(Compound, Result), Result);
187 return;
188 }
189
190 if (const auto *Typedef =
191 Result.Nodes.getNodeAs<TypedefDecl>("lock-guard-typedef")) {
192 diagOnSourceInfo(Typedef->getTypeSourceInfo(), Result);
193 return;
194 }
195
196 if (const auto *UsingAlias =
197 Result.Nodes.getNodeAs<TypeAliasDecl>("lock-guard-using-alias")) {
198 diagOnSourceInfo(UsingAlias->getTypeSourceInfo(), Result);
199 return;
200 }
201
202 if (const auto *Using =
203 Result.Nodes.getNodeAs<UsingDecl>("lock-guard-using-decl")) {
204 diagOnUsingDecl(Using, Result);
205 }
206}
207
208void UseScopedLockCheck::diagOnSingleLock(
209 const VarDecl *LockGuard, const MatchFinder::MatchResult &Result) {
210 auto Diag = diag(LockGuard->getBeginLoc(), UseScopedLockMessage);
211
212 const SourceRange LockGuardTypeRange =
213 getLockGuardRange(LockGuard->getTypeSourceInfo());
214
215 if (LockGuardTypeRange.isInvalid())
216 return;
217
218 // Create Fix-its only if we can find the constructor call to properly handle
219 // 'std::lock_guard l(m, std::adopt_lock)' case.
220 const auto *CtorCall = dyn_cast<CXXConstructExpr>(LockGuard->getInit());
221 if (!CtorCall)
222 return;
223
224 if (CtorCall->getNumArgs() == 1) {
225 Diag << FixItHint::CreateReplacement(LockGuardTypeRange,
226 "std::scoped_lock");
227 return;
228 }
229
230 if (CtorCall->getNumArgs() == 2) {
231 const Expr *const *CtorArgs = CtorCall->getArgs();
232
233 const Expr *MutexArg = CtorArgs[0];
234 const Expr *AdoptLockArg = CtorArgs[1];
235
236 const StringRef MutexSourceText = Lexer::getSourceText(
237 CharSourceRange::getTokenRange(MutexArg->getSourceRange()),
238 *Result.SourceManager, Result.Context->getLangOpts());
239 const StringRef AdoptLockSourceText = Lexer::getSourceText(
240 CharSourceRange::getTokenRange(AdoptLockArg->getSourceRange()),
241 *Result.SourceManager, Result.Context->getLangOpts());
242
243 Diag << FixItHint::CreateReplacement(LockGuardTypeRange, "std::scoped_lock")
244 << FixItHint::CreateReplacement(
245 SourceRange(MutexArg->getBeginLoc(), AdoptLockArg->getEndLoc()),
246 (llvm::Twine(AdoptLockSourceText) + ", " + MutexSourceText)
247 .str());
248 return;
249 }
250
251 llvm_unreachable("Invalid argument number of std::lock_guard constructor");
252}
253
254void UseScopedLockCheck::diagOnMultipleLocks(
255 const llvm::SmallVector<llvm::SmallVector<const VarDecl *>> &LockGroups,
256 const ast_matchers::MatchFinder::MatchResult &Result) {
257 for (const llvm::SmallVector<const VarDecl *> &Group : LockGroups) {
258 if (Group.size() == 1) {
259 if (WarnOnSingleLocks)
260 diagOnSingleLock(Group[0], Result);
261 } else {
262 diag(Group[0]->getBeginLoc(),
263 "use single 'std::scoped_lock' instead of multiple "
264 "'std::lock_guard'");
265
266 for (const VarDecl *Lock : llvm::drop_begin(Group))
267 diag(Lock->getLocation(), "additional 'std::lock_guard' declared here",
268 DiagnosticIDs::Note);
269 }
270 }
271}
272
273void UseScopedLockCheck::diagOnSourceInfo(
274 const TypeSourceInfo *LockGuardSourceInfo,
275 const ast_matchers::MatchFinder::MatchResult &Result) {
276 const TypeLoc TL = LockGuardSourceInfo->getTypeLoc();
277
278 if (const auto TTL = TL.getAs<TemplateSpecializationTypeLoc>()) {
279 auto Diag = diag(TTL.getBeginLoc(), UseScopedLockMessage);
280
281 const SourceRange LockGuardRange =
282 getLockGuardNameRange(LockGuardSourceInfo);
283 if (LockGuardRange.isInvalid())
284 return;
285
286 Diag << FixItHint::CreateReplacement(LockGuardRange, "scoped_lock");
287 }
288}
289
290void UseScopedLockCheck::diagOnUsingDecl(
291 const UsingDecl *UsingDecl,
292 const ast_matchers::MatchFinder::MatchResult &Result) {
293 diag(UsingDecl->getLocation(), UseScopedLockMessage)
294 << FixItHint::CreateReplacement(UsingDecl->getLocation(), "scoped_lock");
295}
296
297} // namespace clang::tidy::modernize
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
UseScopedLockCheck(StringRef Name, ClangTidyContext *Context)
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
static bool isLockGuard(const QualType &Type)
static llvm::SmallVector< llvm::SmallVector< const VarDecl * > > findLocksInCompoundStmt(const CompoundStmt *Block, const ast_matchers::MatchFinder::MatchResult &Result)
static SourceRange getLockGuardNameRange(const TypeSourceInfo *SourceInfo)
static SourceRange getLockGuardRange(const TypeSourceInfo *SourceInfo)
static bool isLockGuardDecl(const NamedDecl *Decl)
static llvm::SmallVector< const VarDecl * > getLockGuardsFromDecl(const DeclStmt *DS)
static const StringRef UseScopedLockMessage
llvm::StringMap< ClangTidyValue > OptionMap