1 //===--- TidyProvider.cpp - create options for running clang-tidy----------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "TidyProvider.h"
10 #include "../clang-tidy/ClangTidyModuleRegistry.h"
11 #include "../clang-tidy/ClangTidyOptions.h"
13 #include "support/FileCache.h"
14 #include "support/Logger.h"
15 #include "support/Path.h"
16 #include "support/ThreadsafeFS.h"
17 #include "llvm/ADT/STLExtras.h"
18 #include "llvm/ADT/SmallString.h"
19 #include "llvm/ADT/StringExtras.h"
20 #include "llvm/ADT/StringSet.h"
21 #include "llvm/Support/Allocator.h"
22 #include "llvm/Support/Process.h"
23 #include "llvm/Support/SourceMgr.h"
31 // Access to config from a .clang-tidy file, caching IO and parsing.
32 class DotClangTidyCache
: private FileCache
{
33 // We cache and expose shared_ptr to avoid copying the value on every lookup
34 // when we're ultimately just going to pass it to mergeWith.
35 mutable std::shared_ptr
<const tidy::ClangTidyOptions
> Value
;
38 DotClangTidyCache(PathRef Path
) : FileCache(Path
) {}
40 std::shared_ptr
<const tidy::ClangTidyOptions
>
41 get(const ThreadsafeFS
&TFS
,
42 std::chrono::steady_clock::time_point FreshTime
) const {
43 std::shared_ptr
<const tidy::ClangTidyOptions
> Result
;
46 [this](std::optional
<llvm::StringRef
> Data
) {
48 if (Data
&& !Data
->empty()) {
49 tidy::DiagCallback Diagnostics
= [](const llvm::SMDiagnostic
&D
) {
50 switch (D
.getKind()) {
51 case llvm::SourceMgr::DK_Error
:
52 elog("tidy-config error at {0}:{1}:{2}: {3}", D
.getFilename(),
53 D
.getLineNo(), D
.getColumnNo(), D
.getMessage());
55 case llvm::SourceMgr::DK_Warning
:
56 log("tidy-config warning at {0}:{1}:{2}: {3}", D
.getFilename(),
57 D
.getLineNo(), D
.getColumnNo(), D
.getMessage());
59 case llvm::SourceMgr::DK_Note
:
60 case llvm::SourceMgr::DK_Remark
:
61 vlog("tidy-config note at {0}:{1}:{2}: {3}", D
.getFilename(),
62 D
.getLineNo(), D
.getColumnNo(), D
.getMessage());
66 if (auto Parsed
= tidy::parseConfigurationWithDiags(
67 llvm::MemoryBufferRef(*Data
, path()), Diagnostics
))
68 Value
= std::make_shared
<const tidy::ClangTidyOptions
>(
71 elog("Error parsing clang-tidy configuration in {0}: {1}", path(),
72 Parsed
.getError().message());
75 [&]() { Result
= Value
; });
80 // Access to combined config from .clang-tidy files governing a source file.
81 // Each config file is cached and the caches are shared for affected sources.
83 // FIXME: largely duplicates config::Provider::fromAncestorRelativeYAMLFiles.
84 // Potentially useful for compile_commands.json too. Extract?
85 class DotClangTidyTree
{
86 const ThreadsafeFS
&FS
;
88 std::chrono::steady_clock::duration MaxStaleness
;
90 mutable std::mutex Mu
;
91 // Keys are the ancestor directory, not the actual config path within it.
92 // We only insert into this map, so pointers to values are stable forever.
93 // Mutex guards the map itself, not the values (which are threadsafe).
94 mutable llvm::StringMap
<DotClangTidyCache
> Cache
;
97 DotClangTidyTree(const ThreadsafeFS
&FS
)
98 : FS(FS
), RelPath(".clang-tidy"), MaxStaleness(std::chrono::seconds(5)) {}
100 void apply(tidy::ClangTidyOptions
&Result
, PathRef AbsPath
) {
101 namespace path
= llvm::sys::path
;
102 assert(path::is_absolute(AbsPath
));
104 // Compute absolute paths to all ancestors (substrings of P.Path).
105 // Ensure cache entries for each ancestor exist in the map.
106 llvm::SmallVector
<DotClangTidyCache
*> Caches
;
108 std::lock_guard
<std::mutex
> Lock(Mu
);
109 for (auto Ancestor
= absoluteParent(AbsPath
); !Ancestor
.empty();
110 Ancestor
= absoluteParent(Ancestor
)) {
111 auto It
= Cache
.find(Ancestor
);
112 // Assemble the actual config file path only if needed.
113 if (It
== Cache
.end()) {
114 llvm::SmallString
<256> ConfigPath
= Ancestor
;
115 path::append(ConfigPath
, RelPath
);
116 It
= Cache
.try_emplace(Ancestor
, ConfigPath
.str()).first
;
118 Caches
.push_back(&It
->second
);
121 // Finally query each individual file.
122 // This will take a (per-file) lock for each file that actually exists.
123 std::chrono::steady_clock::time_point FreshTime
=
124 std::chrono::steady_clock::now() - MaxStaleness
;
125 llvm::SmallVector
<std::shared_ptr
<const tidy::ClangTidyOptions
>>
127 for (const DotClangTidyCache
*Cache
: Caches
)
128 if (auto Config
= Cache
->get(FS
, FreshTime
)) {
129 OptionStack
.push_back(std::move(Config
));
130 if (!OptionStack
.back()->InheritParentConfig
.value_or(false))
134 for (auto &Option
: llvm::reverse(OptionStack
))
135 Result
.mergeWith(*Option
, Order
++);
141 static void mergeCheckList(std::optional
<std::string
> &Checks
,
142 llvm::StringRef List
) {
145 if (!Checks
|| Checks
->empty()) {
146 Checks
.emplace(List
);
149 *Checks
= llvm::join_items(",", *Checks
, List
);
152 TidyProviderRef
provideEnvironment() {
153 static const std::optional
<std::string
> User
= [] {
154 std::optional
<std::string
> Ret
= llvm::sys::Process::GetEnv("USER");
157 return llvm::sys::Process::GetEnv("USERNAME");
164 [](tidy::ClangTidyOptions
&Opts
, llvm::StringRef
) { Opts
.User
= User
; };
165 // FIXME: Once function_ref and unique_function operator= operators handle
166 // null values, this can return null.
167 return [](tidy::ClangTidyOptions
&, llvm::StringRef
) {};
170 TidyProviderRef
provideDefaultChecks() {
171 // These default checks are chosen for:
172 // - low false-positive rate
173 // - providing a lot of value
174 // - being reasonably efficient
175 static const std::string DefaultChecks
= llvm::join_items(
176 ",", "readability-misleading-indentation", "readability-deleted-default",
177 "bugprone-integer-division", "bugprone-sizeof-expression",
178 "bugprone-suspicious-missing-comma", "bugprone-unused-raii",
179 "bugprone-unused-return-value", "misc-unused-using-decls",
180 "misc-unused-alias-decls", "misc-definitions-in-headers");
181 return [](tidy::ClangTidyOptions
&Opts
, llvm::StringRef
) {
182 if (!Opts
.Checks
|| Opts
.Checks
->empty())
183 Opts
.Checks
= DefaultChecks
;
187 TidyProvider
addTidyChecks(llvm::StringRef Checks
,
188 llvm::StringRef WarningsAsErrors
) {
189 return [Checks
= std::string(Checks
),
190 WarningsAsErrors
= std::string(WarningsAsErrors
)](
191 tidy::ClangTidyOptions
&Opts
, llvm::StringRef
) {
192 mergeCheckList(Opts
.Checks
, Checks
);
193 mergeCheckList(Opts
.WarningsAsErrors
, WarningsAsErrors
);
197 TidyProvider
disableUnusableChecks(llvm::ArrayRef
<std::string
> ExtraBadChecks
) {
198 constexpr llvm::StringLiteral
Seperator(",");
199 static const std::string BadChecks
= llvm::join_items(
201 // We want this list to start with a seperator to
202 // simplify appending in the lambda. So including an
203 // empty string here will force that.
205 // include-cleaner is directly integrated in IncludeCleaner.cpp
206 "-misc-include-cleaner",
208 // ----- False Positives -----
210 // Check relies on seeing ifndef/define/endif directives,
211 // clangd doesn't replay those when using a preamble.
212 "-llvm-header-guard", "-modernize-macro-to-enum",
214 // ----- Crashing Checks -----
216 // Check can choke on invalid (intermediate) c++
217 // code, which is often the case when clangd
218 // tries to build an AST.
219 "-bugprone-use-after-move",
220 // Alias for bugprone-use-after-move.
221 "-hicpp-invalid-access-moved",
222 // Check uses dataflow analysis, which might hang/crash unexpectedly on
224 "-bugprone-unchecked-optional-access",
226 // ----- Performance problems -----
228 // This check runs expensive analysis for each variable.
229 // It has been observed to increase reparse time by 10x.
230 "-misc-const-correctness");
232 size_t Size
= BadChecks
.size();
233 for (const std::string
&Str
: ExtraBadChecks
) {
236 Size
+= Seperator
.size();
237 if (LLVM_LIKELY(Str
.front() != '-'))
241 std::string DisableGlob
;
242 DisableGlob
.reserve(Size
);
243 DisableGlob
+= BadChecks
;
244 for (const std::string
&Str
: ExtraBadChecks
) {
247 DisableGlob
+= Seperator
;
248 if (LLVM_LIKELY(Str
.front() != '-'))
249 DisableGlob
.push_back('-');
253 return [DisableList(std::move(DisableGlob
))](tidy::ClangTidyOptions
&Opts
,
255 if (Opts
.Checks
&& !Opts
.Checks
->empty())
256 Opts
.Checks
->append(DisableList
);
260 TidyProviderRef
provideClangdConfig() {
261 return [](tidy::ClangTidyOptions
&Opts
, llvm::StringRef
) {
262 const auto &CurTidyConfig
= Config::current().Diagnostics
.ClangTidy
;
263 if (!CurTidyConfig
.Checks
.empty())
264 mergeCheckList(Opts
.Checks
, CurTidyConfig
.Checks
);
266 for (const auto &CheckOption
: CurTidyConfig
.CheckOptions
)
267 Opts
.CheckOptions
.insert_or_assign(CheckOption
.getKey(),
268 tidy::ClangTidyOptions::ClangTidyValue(
269 CheckOption
.getValue(), 10000U));
273 TidyProvider
provideClangTidyFiles(ThreadsafeFS
&TFS
) {
274 return [Tree
= std::make_unique
<DotClangTidyTree
>(TFS
)](
275 tidy::ClangTidyOptions
&Opts
, llvm::StringRef Filename
) {
276 Tree
->apply(Opts
, Filename
);
280 TidyProvider
combine(std::vector
<TidyProvider
> Providers
) {
281 // FIXME: Once function_ref and unique_function operator= operators handle
282 // null values, we should filter out any Providers that are null. Right now we
283 // have to ensure we dont pass any providers that are null.
284 return [Providers(std::move(Providers
))](tidy::ClangTidyOptions
&Opts
,
285 llvm::StringRef Filename
) {
286 for (const auto &Provider
: Providers
)
287 Provider(Opts
, Filename
);
291 tidy::ClangTidyOptions
getTidyOptionsForFile(TidyProviderRef Provider
,
292 llvm::StringRef Filename
) {
293 // getDefaults instantiates all check factories, which are registered at link
294 // time. So cache the results once.
295 static const auto *DefaultOpts
= [] {
296 auto *Opts
= new tidy::ClangTidyOptions
;
297 *Opts
= tidy::ClangTidyOptions::getDefaults();
298 Opts
->Checks
->clear();
301 auto Opts
= *DefaultOpts
;
303 Provider(Opts
, Filename
);
307 bool isRegisteredTidyCheck(llvm::StringRef Check
) {
308 assert(!Check
.empty());
309 assert(!Check
.contains('*') && !Check
.contains(',') &&
310 "isRegisteredCheck doesn't support globs");
311 assert(Check
.ltrim().front() != '-');
313 static const llvm::StringSet
<llvm::BumpPtrAllocator
> AllChecks
= [] {
314 llvm::StringSet
<llvm::BumpPtrAllocator
> Result
;
315 tidy::ClangTidyCheckFactories Factories
;
316 for (tidy::ClangTidyModuleRegistry::entry E
:
317 tidy::ClangTidyModuleRegistry::entries())
318 E
.instantiate()->addCheckFactories(Factories
);
319 for (const auto &Factory
: Factories
)
320 Result
.insert(Factory
.getKey());
324 return AllChecks
.contains(Check
);
327 std::optional
<bool> isFastTidyCheck(llvm::StringRef Check
) {
328 static auto &Fast
= *new llvm::StringMap
<bool>{
329 #define FAST(CHECK, TIME) {#CHECK,true},
330 #define SLOW(CHECK, TIME) {#CHECK,false},
331 #include "TidyFastChecks.inc"
333 if (auto It
= Fast
.find(Check
); It
!= Fast
.end())
338 } // namespace clangd