clang-tools 24.0.0git
UseScopedLockCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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 isScopedLockDecl(const NamedDecl *Decl) {
31 return Decl->getDeclName().isIdentifier() &&
32 Decl->getName() == "scoped_lock" && Decl->isInStdNamespace();
33}
34
35static const NamedDecl *getLockClassDecl(const QualType &Type) {
36 if (const auto *Record = Type->getAsCanonical<RecordType>())
37 return Record->getDecl();
38
39 if (const auto *TemplateSpecType = Type->getAs<TemplateSpecializationType>())
40 return TemplateSpecType->getTemplateName().getAsTemplateDecl();
41
42 return nullptr;
43}
44
45static bool isLockGuard(const QualType &Type) {
46 if (const NamedDecl *LockDecl = getLockClassDecl(Type))
47 return isLockGuardDecl(LockDecl);
48 return false;
49}
50
51static bool isScopedLock(const QualType &Type) {
52 if (const NamedDecl *LockDecl = getLockClassDecl(Type))
53 return isScopedLockDecl(LockDecl);
54 return false;
55}
56
57static StringRef getLockClassName(const QualType &Type) {
58 const NamedDecl *LockDecl = getLockClassDecl(Type);
59 assert(LockDecl);
60 return LockDecl->getName();
61}
62
65
66 for (const Decl *Decl : DS->decls()) {
67 if (const auto *VD = dyn_cast<VarDecl>(Decl)) {
68 const QualType Type =
69 VD->getType().getCanonicalType().getUnqualifiedType();
70 if (isLockGuard(Type) || isScopedLock(Type))
71 LockGuards.push_back(VD);
72 }
73 }
74
75 return LockGuards;
76}
77
78// Scans through the statements in a block and groups consecutive
79// 'std::lock_guard' variable declarations together.
81findLocksInCompoundStmt(const CompoundStmt *Block,
82 const ast_matchers::MatchFinder::MatchResult &Result) {
83 // store groups of consecutive 'std::lock_guard' declarations
85 SmallVector<const VarDecl *> CurrentLockGuardGroup;
86
87 auto AddAndClearCurrentGroup = [&]() {
88 if (!CurrentLockGuardGroup.empty()) {
89 LockGuardGroups.push_back(CurrentLockGuardGroup);
90 CurrentLockGuardGroup.clear();
91 }
92 };
93
94 for (const Stmt *Stmt : Block->body()) {
95 if (const auto *DS = dyn_cast<DeclStmt>(Stmt)) {
97
98 if (!LockGuards.empty()) {
99 CurrentLockGuardGroup.append(LockGuards);
100 continue;
101 }
102 }
103 AddAndClearCurrentGroup();
104 }
105
106 AddAndClearCurrentGroup();
107
108 return LockGuardGroups;
109}
110
111// Find the exact source range of the 'lock_guard' token
112static SourceRange getLockGuardRange(const TypeSourceInfo *SourceInfo) {
113 const TypeLoc LockGuardTypeLoc = SourceInfo->getTypeLoc();
114
115 return {LockGuardTypeLoc.getBeginLoc(), LockGuardTypeLoc.getEndLoc()};
116}
117
118// Find the exact source range of the 'lock_guard' name token
119static SourceRange getLockGuardNameRange(const TypeSourceInfo *SourceInfo) {
120 const auto TemplateLoc =
121 SourceInfo->getTypeLoc().getAs<TemplateSpecializationTypeLoc>();
122 if (!TemplateLoc)
123 return {};
124
125 return {TemplateLoc.getTemplateNameLoc(),
126 TemplateLoc.getLAngleLoc().getLocWithOffset(-1)};
127}
128
129const static StringRef UseScopedLockMessage =
130 "use 'std::scoped_lock' instead of 'std::lock_guard'";
131
133 ClangTidyContext *Context)
134 : ClangTidyCheck(Name, Context),
135 WarnOnSingleLocks(Options.get("WarnOnSingleLocks", true)),
136 WarnOnUsingAndTypedef(Options.get("WarnOnUsingAndTypedef", true)) {}
137
139 Options.store(Opts, "WarnOnSingleLocks", WarnOnSingleLocks);
140 Options.store(Opts, "WarnOnUsingAndTypedef", WarnOnUsingAndTypedef);
141}
142
143void UseScopedLockCheck::registerMatchers(MatchFinder *Finder) {
144 const auto LockGuardClassDecl =
145 namedDecl(hasName("lock_guard"), isInStdNamespace());
146 const auto ScopedLockClassDecl =
147 namedDecl(hasName("scoped_lock"), isInStdNamespace());
148 const auto LockClassDecl = anyOf(LockGuardClassDecl, ScopedLockClassDecl);
149
150 const auto LockGuardType = qualType(hasUnqualifiedDesugaredType(
151 mapAnyOf(recordType, templateSpecializationType)
152 .with(hasDeclaration(LockGuardClassDecl))));
153
154 const auto LockType = qualType(hasUnqualifiedDesugaredType(
155 mapAnyOf(recordType, templateSpecializationType)
156 .with(hasDeclaration(LockClassDecl))));
157
158 const auto LockGuardVarDecl = varDecl(hasType(LockGuardType));
159 const auto LockVarDecl = varDecl(hasType(LockType));
160
161 if (WarnOnSingleLocks) {
162 Finder->addMatcher(
163 compoundStmt(
164 has(declStmt(has(LockGuardVarDecl)).bind("lock-decl-single")),
165 unless(has(declStmt(unless(equalsBoundNode("lock-decl-single")),
166 has(LockVarDecl))))),
167 this);
168 }
169
170 Finder->addMatcher(
171 compoundStmt(has(declStmt(has(LockVarDecl)).bind("lock-decl-multiple")),
172 has(declStmt(unless(equalsBoundNode("lock-decl-multiple")),
173 has(LockVarDecl))))
174 .bind("block-multiple"),
175 this);
176
177 if (WarnOnUsingAndTypedef) {
178 // Match 'typedef std::lock_guard<std::mutex> Lock'
179 Finder->addMatcher(typedefDecl(hasType(hasUnderlyingType(LockGuardType)))
180 .bind("lock-guard-typedef"),
181 this);
182
183 // Match 'using Lock = std::lock_guard<std::mutex>'
184 Finder->addMatcher(typeAliasDecl(hasType(templateSpecializationType(
185 hasDeclaration(LockGuardClassDecl))))
186 .bind("lock-guard-using-alias"),
187 this);
188
189 // Match 'using std::lock_guard'
190 Finder->addMatcher(
191 usingDecl(hasAnyUsingShadowDecl(hasTargetDecl(LockGuardClassDecl)))
192 .bind("lock-guard-using-decl"),
193 this);
194 }
195}
196
197void UseScopedLockCheck::check(const MatchFinder::MatchResult &Result) {
198 if (const auto *DS = Result.Nodes.getNodeAs<DeclStmt>("lock-decl-single")) {
200 diagOnMultipleLocks({Decls}, Result);
201 return;
202 }
203
204 if (const auto *Compound =
205 Result.Nodes.getNodeAs<CompoundStmt>("block-multiple")) {
206 diagOnMultipleLocks(findLocksInCompoundStmt(Compound, Result), Result);
207 return;
208 }
209
210 if (const auto *Typedef =
211 Result.Nodes.getNodeAs<TypedefDecl>("lock-guard-typedef")) {
212 diagOnSourceInfo(Typedef->getTypeSourceInfo(), Result);
213 return;
214 }
215
216 if (const auto *UsingAlias =
217 Result.Nodes.getNodeAs<TypeAliasDecl>("lock-guard-using-alias")) {
218 diagOnSourceInfo(UsingAlias->getTypeSourceInfo(), Result);
219 return;
220 }
221
222 if (const auto *Using =
223 Result.Nodes.getNodeAs<UsingDecl>("lock-guard-using-decl")) {
224 diagOnUsingDecl(Using, Result);
225 }
226}
227
228void UseScopedLockCheck::diagOnSingleLock(
229 const VarDecl *LockGuard, const MatchFinder::MatchResult &Result) {
230 auto Diag = diag(LockGuard->getBeginLoc(), UseScopedLockMessage);
231
232 const SourceRange LockGuardTypeRange =
233 getLockGuardRange(LockGuard->getTypeSourceInfo());
234
235 if (LockGuardTypeRange.isInvalid())
236 return;
237
238 // Create Fix-its only if we can find the constructor call to properly handle
239 // 'std::lock_guard l(m, std::adopt_lock)' case.
240 const auto *CtorCall =
241 dyn_cast_if_present<CXXConstructExpr>(LockGuard->getInit());
242 if (!CtorCall)
243 return;
244
245 if (CtorCall->getNumArgs() == 1) {
246 Diag << FixItHint::CreateReplacement(LockGuardTypeRange,
247 "std::scoped_lock");
248 return;
249 }
250
251 if (CtorCall->getNumArgs() == 2) {
252 const Expr *const *CtorArgs = CtorCall->getArgs();
253
254 const Expr *MutexArg = CtorArgs[0];
255 const Expr *AdoptLockArg = CtorArgs[1];
256
257 const StringRef MutexSourceText = Lexer::getSourceText(
258 CharSourceRange::getTokenRange(MutexArg->getSourceRange()),
259 *Result.SourceManager, Result.Context->getLangOpts());
260 const StringRef AdoptLockSourceText = Lexer::getSourceText(
261 CharSourceRange::getTokenRange(AdoptLockArg->getSourceRange()),
262 *Result.SourceManager, Result.Context->getLangOpts());
263
264 Diag << FixItHint::CreateReplacement(LockGuardTypeRange, "std::scoped_lock")
265 << FixItHint::CreateReplacement(
266 SourceRange(MutexArg->getBeginLoc(), AdoptLockArg->getEndLoc()),
267 (llvm::Twine(AdoptLockSourceText) + ", " + MutexSourceText)
268 .str());
269 return;
270 }
271
272 llvm_unreachable("Invalid argument number of std::lock_guard constructor");
273}
274
275void UseScopedLockCheck::diagOnMultipleLocks(
276 const SmallVector<SmallVector<const VarDecl *>> &LockGroups,
277 const ast_matchers::MatchFinder::MatchResult &Result) {
278 for (const SmallVector<const VarDecl *> &Group : LockGroups) {
279 if (Group.size() == 1) {
280 if (WarnOnSingleLocks &&
282 Group[0]->getType().getCanonicalType().getUnqualifiedType()))
283 diagOnSingleLock(Group[0], Result);
284 continue;
285 }
286
287 diag(Group[0]->getBeginLoc(),
288 "use single 'std::scoped_lock' instead of multiple locks");
289
290 for (const VarDecl *Lock : llvm::drop_begin(Group)) {
291 const QualType Type =
292 Lock->getType().getCanonicalType().getUnqualifiedType();
293 diag(Lock->getLocation(),
294 (llvm::Twine("additional 'std::") + getLockClassName(Type) +
295 "' declared here")
296 .str(),
297 DiagnosticIDs::Note);
298 }
299 }
300}
301
302void UseScopedLockCheck::diagOnSourceInfo(
303 const TypeSourceInfo *LockGuardSourceInfo,
304 const ast_matchers::MatchFinder::MatchResult &Result) {
305 const TypeLoc TL = LockGuardSourceInfo->getTypeLoc();
306
307 if (const auto TTL = TL.getAs<TemplateSpecializationTypeLoc>()) {
308 auto Diag = diag(TTL.getBeginLoc(), UseScopedLockMessage);
309
310 const SourceRange LockGuardRange =
311 getLockGuardNameRange(LockGuardSourceInfo);
312 if (LockGuardRange.isInvalid())
313 return;
314
315 Diag << FixItHint::CreateReplacement(LockGuardRange, "scoped_lock");
316 }
317}
318
319void UseScopedLockCheck::diagOnUsingDecl(
320 const UsingDecl *UsingDecl,
321 const ast_matchers::MatchFinder::MatchResult &Result) {
322 diag(UsingDecl->getLocation(), UseScopedLockMessage)
323 << FixItHint::CreateReplacement(UsingDecl->getLocation(), "scoped_lock");
324}
325
326} // 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
@ Type
An inlay hint that for a type annotation.
Definition Protocol.h:1731
static bool isLockGuard(const QualType &Type)
static bool isScopedLockDecl(const NamedDecl *Decl)
static SourceRange getLockGuardNameRange(const TypeSourceInfo *SourceInfo)
static SourceRange getLockGuardRange(const TypeSourceInfo *SourceInfo)
static StringRef getLockClassName(const QualType &Type)
static bool isLockGuardDecl(const NamedDecl *Decl)
static SmallVector< SmallVector< const VarDecl * > > findLocksInCompoundStmt(const CompoundStmt *Block, const ast_matchers::MatchFinder::MatchResult &Result)
static const NamedDecl * getLockClassDecl(const QualType &Type)
static bool isScopedLock(const QualType &Type)
static SmallVector< const VarDecl * > getLockGuardsFromDecl(const DeclStmt *DS)
static const StringRef UseScopedLockMessage
llvm::StringMap< ClangTidyValue > OptionMap