clang-tools 22.0.0git
TidyProvider.cpp
Go to the documentation of this file.
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"
12#include "Config.h"
13#include "support/FileCache.h"
14#include "support/Logger.h"
15#include "support/Path.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
27namespace clang {
28namespace clangd {
29namespace {
30
31// Access to config from a .clang-tidy file, caching IO and parsing.
32class 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
37public:
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?
85class 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
96public:
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
141static 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
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
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
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
197TidyProvider 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 "-cppcoreguidelines-macro-to-enum",
214
215 // ----- Crashing Checks -----
216
217 // Check can choke on invalid (intermediate) c++
218 // code, which is often the case when clangd
219 // tries to build an AST.
220 "-bugprone-use-after-move",
221 // Alias for bugprone-use-after-move.
222 "-hicpp-invalid-access-moved",
223 // Check uses dataflow analysis, which might hang/crash unexpectedly on
224 // incomplete code.
225 "-bugprone-unchecked-optional-access");
226
227 size_t Size = BadChecks.size();
228 for (const std::string &Str : ExtraBadChecks) {
229 if (Str.empty())
230 continue;
231 Size += Separator.size();
232 if (LLVM_LIKELY(Str.front() != '-'))
233 ++Size;
234 Size += Str.size();
235 }
236 std::string DisableGlob;
237 DisableGlob.reserve(Size);
238 DisableGlob += BadChecks;
239 for (const std::string &Str : ExtraBadChecks) {
240 if (Str.empty())
241 continue;
242 DisableGlob += Separator;
243 if (LLVM_LIKELY(Str.front() != '-'))
244 DisableGlob.push_back('-');
245 DisableGlob += Str;
246 }
247
248 return [DisableList(std::move(DisableGlob))](tidy::ClangTidyOptions &Opts,
249 llvm::StringRef) {
250 if (Opts.Checks && !Opts.Checks->empty())
251 Opts.Checks->append(DisableList);
252 };
253}
254
256 return [](tidy::ClangTidyOptions &Opts, llvm::StringRef) {
257 const auto &CurTidyConfig = Config::current().Diagnostics.ClangTidy;
258 if (!CurTidyConfig.Checks.empty())
259 mergeCheckList(Opts.Checks, CurTidyConfig.Checks);
260
261 for (const auto &CheckOption : CurTidyConfig.CheckOptions)
262 Opts.CheckOptions.insert_or_assign(CheckOption.getKey(),
264 CheckOption.getValue(), 10000U));
265 };
266}
267
269 return [Tree = std::make_unique<DotClangTidyTree>(TFS)](
270 tidy::ClangTidyOptions &Opts, llvm::StringRef Filename) {
271 Tree->apply(Opts, Filename);
272 };
273}
274
275TidyProvider combine(std::vector<TidyProvider> Providers) {
276 // FIXME: Once function_ref and unique_function operator= operators handle
277 // null values, we should filter out any Providers that are null. Right now we
278 // have to ensure we dont pass any providers that are null.
279 return [Providers(std::move(Providers))](tidy::ClangTidyOptions &Opts,
280 llvm::StringRef Filename) {
281 for (const auto &Provider : Providers)
282 Provider(Opts, Filename);
283 };
284}
285
287 llvm::StringRef Filename) {
288 // getDefaults instantiates all check factories, which are registered at link
289 // time. So cache the results once.
290 static const auto *DefaultOpts = [] {
291 auto *Opts = new tidy::ClangTidyOptions;
293 Opts->Checks->clear();
294 return Opts;
295 }();
296 auto Opts = *DefaultOpts;
297 if (Provider)
298 Provider(Opts, Filename);
299 return Opts;
300}
301
302bool isRegisteredTidyCheck(llvm::StringRef Check) {
303 assert(!Check.empty());
304 assert(!Check.contains('*') && !Check.contains(',') &&
305 "isRegisteredCheck doesn't support globs");
306 assert(Check.ltrim().front() != '-');
307
308 static const llvm::StringSet<llvm::BumpPtrAllocator> AllChecks = [] {
309 llvm::StringSet<llvm::BumpPtrAllocator> Result;
311 for (tidy::ClangTidyModuleRegistry::entry E :
312 tidy::ClangTidyModuleRegistry::entries())
313 E.instantiate()->addCheckFactories(Factories);
314 for (const auto &Factory : Factories)
315 Result.insert(Factory.getKey());
316 return Result;
317 }();
318
319 return AllChecks.contains(Check);
320}
321
322std::optional<bool> isFastTidyCheck(llvm::StringRef Check) {
323 static auto &Fast = *new llvm::StringMap<bool>{
324#define FAST(CHECK, TIME) {#CHECK,true},
325#define SLOW(CHECK, TIME) {#CHECK,false},
326#include "TidyFastChecks.inc"
327 };
328 if (auto It = Fast.find(Check); It != Fast.end())
329 return It->second;
330 return std::nullopt;
331}
332
333} // namespace clangd
334} // namespace clang
static cl::opt< std::string > WarningsAsErrors("warnings-as-errors", desc(R"( Upgrades warnings to errors. Same format as '-checks'. This option's value is appended to the value of the 'WarningsAsErrors' option in .clang-tidy file, if any. )"), cl::init(""), cl::cat(ClangTidyCategory))
static cl::opt< std::string > Config("config", desc(R"( Specifies a configuration in YAML/JSON format: -config="{Checks:' *', CheckOptions:{x:y}}" When the value is empty, clang-tidy will attempt to find a file named .clang-tidy for each source file in its parent directories. )"), cl::init(""), cl::cat(ClangTidyCategory))
static cl::opt< std::string > Checks("checks", desc(R"( Comma-separated list of globs with optional '-' prefix. Globs are processed in order of appearance in the list. Globs without '-' prefix add checks with matching names to the set, globs with the '-' prefix remove checks with matching names from the set of enabled checks. This option's value is appended to the value of the 'Checks' option in .clang-tidy file, if any. )"), cl::init(""), cl::cat(ClangTidyCategory))
const char DefaultChecks[]
Base class for threadsafe cache of data read from a file on disk.
Definition FileCache.h:40
Wrapper for vfs::FileSystem for use in multithreaded programs like clangd.
A collection of ClangTidyCheckFactory instances.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:45
llvm::unique_function< void(tidy::ClangTidyOptions &, llvm::StringRef) const > TidyProvider
A factory to modify a tidy::ClangTidyOptions.
TidyProvider combine(std::vector< TidyProvider > Providers)
bool isRegisteredTidyCheck(llvm::StringRef Check)
Returns if Check is a registered clang-tidy check.
void vlog(const char *Fmt, Ts &&... Vals)
Definition Logger.h:72
llvm::function_ref< void(tidy::ClangTidyOptions &, llvm::StringRef)> TidyProviderRef
A factory to modify a tidy::ClangTidyOptions that doesn't hold any state.
std::optional< bool > isFastTidyCheck(llvm::StringRef Check)
Returns if Check is known-fast, known-slow, or its speed is unknown.
TidyProvider addTidyChecks(llvm::StringRef Checks, llvm::StringRef WarningsAsErrors)
Provider the enables a specific set of checks and warnings as errors.
PathRef absoluteParent(PathRef Path)
Variant of parent_path that operates only on absolute paths.
Definition Path.cpp:22
void log(const char *Fmt, Ts &&... Vals)
Definition Logger.h:67
static void mergeCheckList(std::optional< std::string > &Checks, llvm::StringRef List)
TidyProvider provideClangTidyFiles(ThreadsafeFS &TFS)
Provider that searches for .clang-tidy configuration files in the directory tree.
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition Path.h:29
tidy::ClangTidyOptions getTidyOptionsForFile(TidyProviderRef Provider, llvm::StringRef Filename)
TidyProvider disableUnusableChecks(llvm::ArrayRef< std::string > ExtraBadChecks)
Provider that will disable checks known to not work with clangd.
std::string Path
A typedef to represent a file path.
Definition Path.h:26
TidyProvider provideDefaultChecks()
Provider that will enable a nice set of default checks if none are specified.
TidyProvider provideClangdConfig()
void elog(const char *Fmt, Ts &&... Vals)
Definition Logger.h:61
TidyProvider provideEnvironment()
Provider that just sets the defaults.
llvm::ErrorOr< ClangTidyOptions > parseConfigurationWithDiags(llvm::MemoryBufferRef Config, DiagCallback Handler)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
struct clang::clangd::Config::@224206046260313204212274150166346126315121140114::@177170157270305170147304143303331271375010045063 ClangTidy
Configures what clang-tidy checks to run and options to use with them.
static const Config & current()
Returns the Config of the current Context, or an empty configuration.
Definition Config.cpp:17
struct clang::clangd::Config::@224206046260313204212274150166346126315121140114 Diagnostics
Controls warnings and errors when parsing code.
Helper structure for storing option value with priority of the value.
Contains options for clang-tidy.
OptionMap CheckOptions
Key-value mapping used to store check-specific options.
std::optional< std::string > Checks
Checks filter.
std::optional< std::string > User
Specifies the name or e-mail of the user running clang-tidy.
static ClangTidyOptions getDefaults()
These options are used for all settings that haven't been overridden by the OptionsProvider.