xref: /llvm-project/clang-tools-extra/clangd/TidyProvider.cpp (revision 8a1eba48edc8a9668123a6b925c6dbb7f45a363a)
1 //===--- TidyProvider.cpp - create options for running 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 "TidyProvider.h"
10 #include "../clang-tidy/ClangTidyModuleRegistry.h"
11 #include "../clang-tidy/ClangTidyOptions.h"
12 #include "Config.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"
24 #include <memory>
25 #include <optional>
26 
27 namespace clang {
28 namespace clangd {
29 namespace {
30 
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;
36 
37 public:
38   DotClangTidyCache(PathRef Path) : FileCache(Path) {}
39 
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;
44     read(
45         TFS, FreshTime,
46         [this](std::optional<llvm::StringRef> Data) {
47           Value.reset();
48           if (Data && !Data->empty()) {
49             auto 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());
54                 break;
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());
58                 break;
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());
63                 break;
64               }
65             };
66             if (auto Parsed = tidy::parseConfigurationWithDiags(
67                     llvm::MemoryBufferRef(*Data, path()), Diagnostics))
68               Value = std::make_shared<const tidy::ClangTidyOptions>(
69                   std::move(*Parsed));
70             else
71               elog("Error parsing clang-tidy configuration in {0}: {1}", path(),
72                    Parsed.getError().message());
73           }
74         },
75         [&]() { Result = Value; });
76     return Result;
77   }
78 };
79 
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.
82 //
83 // FIXME: largely duplicates config::Provider::fromAncestorRelativeYAMLFiles.
84 // Potentially useful for compile_commands.json too. Extract?
85 class DotClangTidyTree {
86   const ThreadsafeFS &FS;
87   std::string RelPath;
88   std::chrono::steady_clock::duration MaxStaleness;
89 
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;
95 
96 public:
97   DotClangTidyTree(const ThreadsafeFS &FS)
98       : FS(FS), RelPath(".clang-tidy"), MaxStaleness(std::chrono::seconds(5)) {}
99 
100   void apply(tidy::ClangTidyOptions &Result, PathRef AbsPath) {
101     namespace path = llvm::sys::path;
102     assert(path::is_absolute(AbsPath));
103 
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;
107     {
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;
117         }
118         Caches.push_back(&It->second);
119       }
120     }
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>>
126         OptionStack;
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))
131           break;
132       }
133     unsigned Order = 1u;
134     for (auto &Option : llvm::reverse(OptionStack))
135       Result.mergeWith(*Option, Order++);
136   }
137 };
138 
139 } // namespace
140 
141 static void mergeCheckList(std::optional<std::string> &Checks,
142                            llvm::StringRef List) {
143   if (List.empty())
144     return;
145   if (!Checks || Checks->empty()) {
146     Checks.emplace(List);
147     return;
148   }
149   *Checks = llvm::join_items(",", *Checks, List);
150 }
151 
152 TidyProvider provideEnvironment() {
153   static const std::optional<std::string> User = [] {
154     std::optional<std::string> Ret = llvm::sys::Process::GetEnv("USER");
155 #ifdef _WIN32
156     if (!Ret)
157       return llvm::sys::Process::GetEnv("USERNAME");
158 #endif
159     return Ret;
160   }();
161 
162   if (User)
163     return
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) {};
168 }
169 
170 TidyProvider 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;
184   };
185 }
186 
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);
194   };
195 }
196 
197 TidyProvider disableUnusableChecks(llvm::ArrayRef<std::string> ExtraBadChecks) {
198   constexpr llvm::StringLiteral Separator(",");
199   static const std::string BadChecks = llvm::join_items(
200       Separator,
201       // We want this list to start with a separator to
202       // simplify appending in the lambda. So including an
203       // empty string here will force that.
204       "",
205       // include-cleaner is directly integrated in IncludeCleaner.cpp
206       "-misc-include-cleaner",
207 
208       // ----- False Positives -----
209 
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",
213 
214       // ----- Crashing Checks -----
215 
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
223       // incomplete code.
224       "-bugprone-unchecked-optional-access");
225 
226   size_t Size = BadChecks.size();
227   for (const std::string &Str : ExtraBadChecks) {
228     if (Str.empty())
229       continue;
230     Size += Separator.size();
231     if (LLVM_LIKELY(Str.front() != '-'))
232       ++Size;
233     Size += Str.size();
234   }
235   std::string DisableGlob;
236   DisableGlob.reserve(Size);
237   DisableGlob += BadChecks;
238   for (const std::string &Str : ExtraBadChecks) {
239     if (Str.empty())
240       continue;
241     DisableGlob += Separator;
242     if (LLVM_LIKELY(Str.front() != '-'))
243       DisableGlob.push_back('-');
244     DisableGlob += Str;
245   }
246 
247   return [DisableList(std::move(DisableGlob))](tidy::ClangTidyOptions &Opts,
248                                                llvm::StringRef) {
249     if (Opts.Checks && !Opts.Checks->empty())
250       Opts.Checks->append(DisableList);
251   };
252 }
253 
254 TidyProvider provideClangdConfig() {
255   return [](tidy::ClangTidyOptions &Opts, llvm::StringRef) {
256     const auto &CurTidyConfig = Config::current().Diagnostics.ClangTidy;
257     if (!CurTidyConfig.Checks.empty())
258       mergeCheckList(Opts.Checks, CurTidyConfig.Checks);
259 
260     for (const auto &CheckOption : CurTidyConfig.CheckOptions)
261       Opts.CheckOptions.insert_or_assign(CheckOption.getKey(),
262                                          tidy::ClangTidyOptions::ClangTidyValue(
263                                              CheckOption.getValue(), 10000U));
264   };
265 }
266 
267 TidyProvider provideClangTidyFiles(ThreadsafeFS &TFS) {
268   return [Tree = std::make_unique<DotClangTidyTree>(TFS)](
269              tidy::ClangTidyOptions &Opts, llvm::StringRef Filename) {
270     Tree->apply(Opts, Filename);
271   };
272 }
273 
274 TidyProvider combine(std::vector<TidyProvider> Providers) {
275   // FIXME: Once function_ref and unique_function operator= operators handle
276   // null values, we should filter out any Providers that are null. Right now we
277   // have to ensure we dont pass any providers that are null.
278   return [Providers(std::move(Providers))](tidy::ClangTidyOptions &Opts,
279                                            llvm::StringRef Filename) {
280     for (const auto &Provider : Providers)
281       Provider(Opts, Filename);
282   };
283 }
284 
285 tidy::ClangTidyOptions getTidyOptionsForFile(TidyProviderRef Provider,
286                                              llvm::StringRef Filename) {
287   // getDefaults instantiates all check factories, which are registered at link
288   // time. So cache the results once.
289   static const auto *DefaultOpts = [] {
290     auto *Opts = new tidy::ClangTidyOptions;
291     *Opts = tidy::ClangTidyOptions::getDefaults();
292     Opts->Checks->clear();
293     return Opts;
294   }();
295   auto Opts = *DefaultOpts;
296   if (Provider)
297     Provider(Opts, Filename);
298   return Opts;
299 }
300 
301 bool isRegisteredTidyCheck(llvm::StringRef Check) {
302   assert(!Check.empty());
303   assert(!Check.contains('*') && !Check.contains(',') &&
304          "isRegisteredCheck doesn't support globs");
305   assert(Check.ltrim().front() != '-');
306 
307   static const llvm::StringSet<llvm::BumpPtrAllocator> AllChecks = [] {
308     llvm::StringSet<llvm::BumpPtrAllocator> Result;
309     tidy::ClangTidyCheckFactories Factories;
310     for (tidy::ClangTidyModuleRegistry::entry E :
311          tidy::ClangTidyModuleRegistry::entries())
312       E.instantiate()->addCheckFactories(Factories);
313     for (const auto &Factory : Factories)
314       Result.insert(Factory.getKey());
315     return Result;
316   }();
317 
318   return AllChecks.contains(Check);
319 }
320 
321 std::optional<bool> isFastTidyCheck(llvm::StringRef Check) {
322   static auto &Fast = *new llvm::StringMap<bool>{
323 #define FAST(CHECK, TIME) {#CHECK,true},
324 #define SLOW(CHECK, TIME) {#CHECK,false},
325 #include "TidyFastChecks.inc"
326   };
327   if (auto It = Fast.find(Check); It != Fast.end())
328     return It->second;
329   return std::nullopt;
330 }
331 
332 } // namespace clangd
333 } // namespace clang
334