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 "Config.h"
11 #include "support/FileCache.h"
12 #include "support/Logger.h"
13 #include "support/ThreadsafeFS.h"
14 #include "llvm/ADT/STLExtras.h"
15 #include "llvm/ADT/SmallString.h"
16 #include "llvm/ADT/StringExtras.h"
17 #include "llvm/Support/ErrorHandling.h"
18 #include "llvm/Support/Process.h"
19 #include "llvm/Support/VirtualFileSystem.h"
20 #include <memory>
21 
22 namespace clang {
23 namespace clangd {
24 namespace {
25 
26 // Access to config from a .clang-tidy file, caching IO and parsing.
27 class DotClangTidyCache : private FileCache {
28   // We cache and expose shared_ptr to avoid copying the value on every lookup
29   // when we're ultimately just going to pass it to mergeWith.
30   mutable std::shared_ptr<const tidy::ClangTidyOptions> Value;
31 
32 public:
DotClangTidyCache(PathRef Path)33   DotClangTidyCache(PathRef Path) : FileCache(Path) {}
34 
35   std::shared_ptr<const tidy::ClangTidyOptions>
get(const ThreadsafeFS & TFS,std::chrono::steady_clock::time_point FreshTime) const36   get(const ThreadsafeFS &TFS,
37       std::chrono::steady_clock::time_point FreshTime) const {
38     std::shared_ptr<const tidy::ClangTidyOptions> Result;
39     read(
40         TFS, FreshTime,
41         [this](llvm::Optional<llvm::StringRef> Data) {
42           Value.reset();
43           if (Data && !Data->empty()) {
44             if (auto Parsed = tidy::parseConfiguration(*Data))
45               Value = std::make_shared<const tidy::ClangTidyOptions>(
46                   std::move(*Parsed));
47             else
48               elog("Error parsing clang-tidy configuration in {0}: {1}", path(),
49                    Parsed.getError().message());
50           }
51         },
52         [&]() { Result = Value; });
53     return Result;
54   }
55 };
56 
57 // Access to combined config from .clang-tidy files governing a source file.
58 // Each config file is cached and the caches are shared for affected sources.
59 //
60 // FIXME: largely duplicates config::Provider::fromAncestorRelativeYAMLFiles.
61 // Potentially useful for compile_commands.json too. Extract?
62 class DotClangTidyTree {
63   const ThreadsafeFS &FS;
64   std::string RelPath;
65   std::chrono::steady_clock::duration MaxStaleness;
66 
67   mutable std::mutex Mu;
68   // Keys are the ancestor directory, not the actual config path within it.
69   // We only insert into this map, so pointers to values are stable forever.
70   // Mutex guards the map itself, not the values (which are threadsafe).
71   mutable llvm::StringMap<DotClangTidyCache> Cache;
72 
73 public:
DotClangTidyTree(const ThreadsafeFS & FS)74   DotClangTidyTree(const ThreadsafeFS &FS)
75       : FS(FS), RelPath(".clang-tidy"), MaxStaleness(std::chrono::seconds(5)) {}
76 
apply(tidy::ClangTidyOptions & Result,PathRef AbsPath)77   void apply(tidy::ClangTidyOptions &Result, PathRef AbsPath) {
78     namespace path = llvm::sys::path;
79     assert(path::is_absolute(AbsPath));
80 
81     // Compute absolute paths to all ancestors (substrings of P.Path).
82     // Ensure cache entries for each ancestor exist in the map.
83     llvm::StringRef Parent = path::parent_path(AbsPath);
84     llvm::SmallVector<DotClangTidyCache *, 8> Caches;
85     {
86       std::lock_guard<std::mutex> Lock(Mu);
87       for (auto I = path::begin(Parent, path::Style::posix),
88                 E = path::end(Parent);
89            I != E; ++I) {
90         assert(I->end() >= Parent.begin() && I->end() <= Parent.end() &&
91                "Canonical path components should be substrings");
92         llvm::StringRef Ancestor(Parent.begin(), I->end() - Parent.begin());
93         auto It = Cache.find(Ancestor);
94 
95         // Assemble the actual config file path only if needed.
96         if (It == Cache.end()) {
97           llvm::SmallString<256> ConfigPath = Ancestor;
98           path::append(ConfigPath, RelPath);
99           It = Cache.try_emplace(Ancestor, ConfigPath.str()).first;
100         }
101         Caches.push_back(&It->second);
102       }
103     }
104     // Finally query each individual file.
105     // This will take a (per-file) lock for each file that actually exists.
106     std::chrono::steady_clock::time_point FreshTime =
107         std::chrono::steady_clock::now() - MaxStaleness;
108     llvm::SmallVector<std::shared_ptr<const tidy::ClangTidyOptions>, 4>
109         OptionStack;
110     for (const DotClangTidyCache *Cache : Caches)
111       if (auto Config = Cache->get(FS, FreshTime)) {
112         OptionStack.push_back(std::move(Config));
113         if (!OptionStack.back()->InheritParentConfig.getValueOr(false))
114           break;
115       }
116     unsigned Order = 1u;
117     for (auto &Option : llvm::reverse(OptionStack))
118       Result.mergeWith(*Option, Order++);
119   }
120 };
121 
122 } // namespace
123 
mergeCheckList(llvm::Optional<std::string> & Checks,llvm::StringRef List)124 static void mergeCheckList(llvm::Optional<std::string> &Checks,
125                            llvm::StringRef List) {
126   if (List.empty())
127     return;
128   if (!Checks || Checks->empty()) {
129     Checks.emplace(List);
130     return;
131   }
132   *Checks = llvm::join_items(",", *Checks, List);
133 }
134 
provideEnvironment()135 TidyProviderRef provideEnvironment() {
136   static const llvm::Optional<std::string> User = [] {
137     llvm::Optional<std::string> Ret = llvm::sys::Process::GetEnv("USER");
138 #ifdef _WIN32
139     if (!Ret)
140       return llvm::sys::Process::GetEnv("USERNAME");
141 #endif
142     return Ret;
143   }();
144 
145   if (User)
146     return
147         [](tidy::ClangTidyOptions &Opts, llvm::StringRef) { Opts.User = User; };
148   // FIXME: Once function_ref and unique_function operator= operators handle
149   // null values, this can return null.
150   return [](tidy::ClangTidyOptions &, llvm::StringRef) {};
151 }
152 
provideDefaultChecks()153 TidyProviderRef provideDefaultChecks() {
154   // These default checks are chosen for:
155   //  - low false-positive rate
156   //  - providing a lot of value
157   //  - being reasonably efficient
158   static const std::string DefaultChecks = llvm::join_items(
159       ",", "readability-misleading-indentation", "readability-deleted-default",
160       "bugprone-integer-division", "bugprone-sizeof-expression",
161       "bugprone-suspicious-missing-comma", "bugprone-unused-raii",
162       "bugprone-unused-return-value", "misc-unused-using-decls",
163       "misc-unused-alias-decls", "misc-definitions-in-headers");
164   return [](tidy::ClangTidyOptions &Opts, llvm::StringRef) {
165     if (!Opts.Checks || Opts.Checks->empty())
166       Opts.Checks = DefaultChecks;
167   };
168 }
169 
addTidyChecks(llvm::StringRef Checks,llvm::StringRef WarningsAsErrors)170 TidyProvider addTidyChecks(llvm::StringRef Checks,
171                            llvm::StringRef WarningsAsErrors) {
172   return [Checks = std::string(Checks),
173           WarningsAsErrors = std::string(WarningsAsErrors)](
174              tidy::ClangTidyOptions &Opts, llvm::StringRef) {
175     mergeCheckList(Opts.Checks, Checks);
176     mergeCheckList(Opts.WarningsAsErrors, WarningsAsErrors);
177   };
178 }
179 
disableUnusableChecks(llvm::ArrayRef<std::string> ExtraBadChecks)180 TidyProvider disableUnusableChecks(llvm::ArrayRef<std::string> ExtraBadChecks) {
181   constexpr llvm::StringLiteral Seperator(",");
182   static const std::string BadChecks =
183       llvm::join_items(Seperator,
184                        // We want this list to start with a seperator to
185                        // simplify appending in the lambda. So including an
186                        // empty string here will force that.
187                        "",
188                        // ----- False Positives -----
189 
190                        // Check relies on seeing ifndef/define/endif directives,
191                        // clangd doesn't replay those when using a preamble.
192                        "-llvm-header-guard",
193 
194                        // ----- Crashing Checks -----
195 
196                        // Check can choke on invalid (intermediate) c++
197                        // code, which is often the case when clangd
198                        // tries to build an AST.
199                        "-bugprone-use-after-move");
200 
201   size_t Size = BadChecks.size();
202   for (const std::string &Str : ExtraBadChecks) {
203     if (Str.empty())
204       continue;
205     Size += Seperator.size();
206     if (LLVM_LIKELY(Str.front() != '-'))
207       ++Size;
208     Size += Str.size();
209   }
210   std::string DisableGlob;
211   DisableGlob.reserve(Size);
212   DisableGlob += BadChecks;
213   for (const std::string &Str : ExtraBadChecks) {
214     if (Str.empty())
215       continue;
216     DisableGlob += Seperator;
217     if (LLVM_LIKELY(Str.front() != '-'))
218       DisableGlob.push_back('-');
219     DisableGlob += Str;
220   }
221 
222   return [DisableList(std::move(DisableGlob))](tidy::ClangTidyOptions &Opts,
223                                                llvm::StringRef) {
224     if (Opts.Checks && !Opts.Checks->empty())
225       Opts.Checks->append(DisableList);
226   };
227 }
228 
provideClangdConfig()229 TidyProviderRef provideClangdConfig() {
230   return [](tidy::ClangTidyOptions &Opts, llvm::StringRef) {
231     const auto &CurTidyConfig = Config::current().ClangTidy;
232     if (!CurTidyConfig.Checks.empty())
233       mergeCheckList(Opts.Checks, CurTidyConfig.Checks);
234 
235     for (const auto &CheckOption : CurTidyConfig.CheckOptions)
236       Opts.CheckOptions.insert_or_assign(CheckOption.getKey(),
237                                          tidy::ClangTidyOptions::ClangTidyValue(
238                                              CheckOption.getValue(), 10000U));
239   };
240 }
241 
provideClangTidyFiles(ThreadsafeFS & TFS)242 TidyProvider provideClangTidyFiles(ThreadsafeFS &TFS) {
243   return [Tree = std::make_unique<DotClangTidyTree>(TFS)](
244              tidy::ClangTidyOptions &Opts, llvm::StringRef Filename) {
245     Tree->apply(Opts, Filename);
246   };
247 }
248 
combine(std::vector<TidyProvider> Providers)249 TidyProvider combine(std::vector<TidyProvider> Providers) {
250   // FIXME: Once function_ref and unique_function operator= operators handle
251   // null values, we should filter out any Providers that are null. Right now we
252   // have to ensure we dont pass any providers that are null.
253   return [Providers(std::move(Providers))](tidy::ClangTidyOptions &Opts,
254                                            llvm::StringRef Filename) {
255     for (const auto &Provider : Providers)
256       Provider(Opts, Filename);
257   };
258 }
259 
getTidyOptionsForFile(TidyProviderRef Provider,llvm::StringRef Filename)260 tidy::ClangTidyOptions getTidyOptionsForFile(TidyProviderRef Provider,
261                                              llvm::StringRef Filename) {
262   tidy::ClangTidyOptions Opts = tidy::ClangTidyOptions::getDefaults();
263   Opts.Checks->clear();
264   if (Provider)
265     Provider(Opts, Filename);
266   return Opts;
267 }
268 } // namespace clangd
269 } // namespace clang
270