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

source code of clang-tools-extra/clang-tidy/modernize/UseScopedLockCheck.cpp