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