clang-tools 24.0.0git
ClangTidyFeatureModule.cpp
Go to the documentation of this file.
1//===--- ClangTidyFeatureModule.cpp - clang-tidy integration -------------===//
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
14#include "Config.h"
15#include "Diagnostics.h"
16#include "Feature.h"
17#include "SourceCode.h"
18#include "support/Logger.h"
19#include "support/Trace.h"
20#include "clang/ASTMatchers/ASTMatchFinder.h"
21#include "clang/Basic/Diagnostic.h"
22#include "clang/Basic/DiagnosticIDs.h"
23#include "clang/Basic/FileManager.h"
24#include "clang/Frontend/CompilerInstance.h"
25#include "clang/Lex/Preprocessor.h"
26#include "clang/Lex/PreprocessorOptions.h"
27#include "clang/Tooling/Core/Diagnostic.h"
28#include "llvm/ADT/DenseSet.h"
29#include "llvm/ADT/STLExtras.h"
30#include "llvm/ADT/SmallString.h"
31#include "llvm/Support/MemoryBuffer.h"
32#include "llvm/Support/Path.h"
33#include <memory>
34#include <optional>
35#include <string>
36#include <tuple>
37#include <utility>
38#include <vector>
39
40// Force the linker to link in Clang-tidy modules.
41// clangd doesn't support the static analyzer.
42#if CLANGD_TIDY_CHECKS
43#define CLANG_TIDY_DISABLE_STATIC_ANALYZER_CHECKS
45#endif
46
47#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
49#include <mutex>
50#endif
51
52namespace clang {
53namespace clangd {
54namespace {
55
56// Filter for clang diagnostics groups enabled by CTOptions.Checks.
57//
58// These are check names like clang-diagnostics-unused.
59// Note that unlike -Wunused, clang-diagnostics-unused does not imply
60// subcategories like clang-diagnostics-unused-function.
61//
62// This is used to determine which diagnostics can be enabled by ExtraArgs in
63// the clang-tidy configuration.
64class TidyDiagnosticGroups {
65 // Whether all diagnostic groups are enabled by default.
66 // True if we've seen clang-diagnostic-*.
67 bool Default = false;
68 // Set of diag::Group whose enablement != Default.
69 // If Default is false, this is foo where we've seen clang-diagnostic-foo.
70 llvm::DenseSet<unsigned> Exceptions;
71
72public:
73 TidyDiagnosticGroups(llvm::StringRef Checks) {
74 constexpr llvm::StringLiteral Prefix = "clang-diagnostic-";
75 llvm::StringRef Check;
76 while (!Checks.empty()) {
77 std::tie(Check, Checks) = Checks.split(',');
78 Check = Check.trim();
79 if (Check.empty())
80 continue;
81
82 bool Enable = !Check.consume_front("-");
83 bool Glob = Check.consume_back("*");
84 if (Glob) {
85 // Is this clang-diagnostic-*, or *, or so?
86 // (We ignore all other types of globs).
87 if (Prefix.starts_with(Check)) {
88 Default = Enable;
89 Exceptions.clear();
90 }
91 continue;
92 }
93 // In "*,clang-diagnostic-foo", the latter is a no-op.
94 // The only non-glob entries we care about are clang-diagnostic-foo.
95 if (Default == Enable || !Check.consume_front(Prefix))
96 continue;
97 if (auto Group = DiagnosticIDs::getGroupForWarningOption(Check))
98 Exceptions.insert(static_cast<unsigned>(*Group));
99 }
100 }
101
102 bool operator()(diag::Group Group) const {
103 return Exceptions.contains(static_cast<unsigned>(Group)) ? !Default
104 : Default;
105 }
106};
107
108// Find -W<group> and -Wno-<group> options in ExtraArgs and apply them to Diags.
109//
110// This is used to handle ExtraArgs in clang-tidy configuration.
111// We don't use clang's standard handling of this as we want slightly different
112// behavior (e.g. we want to exclude these from -Wno-error).
113void applyWarningOptions(llvm::ArrayRef<std::string> ExtraArgs,
114 llvm::function_ref<bool(diag::Group)> EnabledGroups,
115 DiagnosticsEngine &Diags) {
116 for (llvm::StringRef Group : ExtraArgs) {
117 // Only handle args that are of the form -W[no-]<group>.
118 // Other flags are possible but rare and deliberately out of scope.
119 llvm::SmallVector<diag::kind> Members;
120 if (!Group.consume_front("-W") || Group.empty())
121 continue;
122 bool Enable = !Group.consume_front("no-");
123 if (Diags.getDiagnosticIDs()->getDiagnosticsInGroup(
124 diag::Flavor::WarningOrError, Group, Members))
125 continue;
126
127 // Upgrade (or downgrade) the severity of each diagnostic in the group.
128 // If -Werror is on, newly added warnings will be treated as errors.
129 // We don't want this, so keep track of them to fix afterwards.
130 bool NeedsWerrorExclusion = false;
131 for (diag::kind ID : Members) {
132 if (Enable) {
133 if (Diags.getDiagnosticLevel(ID, SourceLocation()) >=
134 DiagnosticsEngine::Warning)
135 continue;
136 auto DiagGroup = Diags.getDiagnosticIDs()->getGroupForDiag(ID);
137 if (!DiagGroup || !EnabledGroups(*DiagGroup))
138 continue;
139 Diags.setSeverity(ID, diag::Severity::Warning, SourceLocation());
140 NeedsWerrorExclusion |= Diags.getWarningsAsErrors();
141 } else {
142 Diags.setSeverity(ID, diag::Severity::Ignored, SourceLocation());
143 }
144 }
145 if (NeedsWerrorExclusion) {
146 // FIXME: there's no API to suppress -Werror for single diagnostics.
147 // In some cases with sub-groups, we may end up erroneously
148 // downgrading diagnostics that were -Werror in the compile command.
149 Diags.setDiagnosticGroupWarningAsError(Group, false);
150 }
151 }
152}
153
154tidy::ClangTidyCheckFactories
155filterFastChecks(const tidy::ClangTidyCheckFactories &All,
157 if (Policy == Config::FastCheckPolicy::None)
158 return All;
159 bool AllowUnknown = Policy == Config::FastCheckPolicy::Loose;
160 tidy::ClangTidyCheckFactories Fast;
161 for (const auto &Factory : All)
162 if (isFastTidyCheck(Factory.getKey()).value_or(AllowUnknown))
163 Fast.registerCheckFactory(Factory.first(), Factory.second);
164 return Fast;
165}
166
167class TidyASTListener final : public FeatureModule::ASTListener {
168public:
169 explicit TidyASTListener(std::shared_ptr<const TidyProvider> OptionsProvider)
170 : Provider(std::move(OptionsProvider)) {}
171
172 void beforeBeginSourceFile(CompilerInstance &CI) override {
173 // Warning options must precede frontend initialization: module loading in
174 // BeginSourceFile() can consult their severity to decide whether to fail.
175 const auto &Inputs = CI.getFrontendOpts().Inputs;
176 if (Inputs.empty() || !Inputs.front().isFile()) {
177 elog("Cannot configure clang-tidy without a main-file input");
178 return;
179 }
180 llvm::SmallString<256> Path(Inputs.front().getFile());
181 // The remapped buffer carries clangd's original main-file spelling.
182 for (const auto &Remap :
183 llvm::reverse(CI.getPreprocessorOpts().RemappedFileBuffers)) {
184 if (Remap.first == Inputs.front().getFile()) {
185 Path = Remap.second->getBufferIdentifier();
186 break;
187 }
188 }
189 // Providers search parent directories for .clang-tidy and require an
190 // absolute path. Respect the compiler's working directory, not clangd's.
191 if (!llvm::sys::path::is_absolute(Path))
192 CI.getFileManager().makeAbsolutePath(Path, /*Canonicalize=*/true);
193 if (!llvm::sys::path::is_absolute(Path)) {
194 elog("Cannot resolve clang-tidy input path: {0}", Path);
195 return;
196 }
197 Filename = Path.str().str();
198 trace::Span Tracer("ClangTidyOpts");
199 Options = getTidyOptionsForFile(*Provider, Filename);
200 dlog("ClangTidy configuration for file {0}: {1}", Filename,
201 tidy::configurationAsText(*Options));
202
203 // If clang-tidy is configured to emit clang warnings, we should too.
204 //
205 // Such clang-tidy configuration consists of two parts:
206 // - ExtraArgs: ["-Wfoo"] causes clang to produce the warnings
207 // - Checks: "clang-diagnostic-foo" prevents clang-tidy filtering them out
208 //
209 // In clang-tidy, diagnostics are emitted if they pass both checks.
210 // When groups contain subgroups, -Wparent includes the child, but
211 // clang-diagnostic-parent does not.
212 //
213 // We *don't* want to change the compile command directly. This can have
214 // too many unexpected effects: breaking the command, interactions with
215 // -- and -Werror, etc. Besides, we've already parsed the command.
216 // Instead we parse the -W<group> flags and handle them directly.
217 //
218 // Similarly, we don't want to use Checks to filter clang diagnostics after
219 // they are generated, as this spreads clang-tidy emulation everywhere.
220 // Instead, we just use these to filter which extra diagnostics we enable.
221 TidyDiagnosticGroups EnabledGroups(Options->Checks ? *Options->Checks
222 : llvm::StringRef());
223 if (Options->ExtraArgsBefore)
224 applyWarningOptions(*Options->ExtraArgsBefore, EnabledGroups,
225 CI.getDiagnostics());
226 if (Options->ExtraArgs)
227 applyWarningOptions(*Options->ExtraArgs, EnabledGroups,
228 CI.getDiagnostics());
229 }
230
231 void beforePPCallbacks(CompilerInstance &CI) override {
232 // Preamble builds don't call beforeBeginSourceFile(). Running tidy there
233 // would duplicate diagnostics and interfere with PCH generation.
234 if (!Options)
235 return;
236
237 // Set up ClangTidy. Must happen after BeginSourceFile() so ASTContext
238 // exists. Clang-tidy has some limitations to ensure reasonable performance:
239 // - checks don't see all preprocessor events in the preamble
240 // - matchers run only over the main-file top-level decls (and can't see
241 // ancestors outside this scope).
242 // In practice almost all checks work well without modifications.
243 trace::Span Tracer("ClangTidyInit");
244 static const auto *AllFactories = [] {
245 auto *Factories = new tidy::ClangTidyCheckFactories;
246 for (const auto &Entry : tidy::ClangTidyModuleRegistry::entries())
247 Entry.instantiate()->addCheckFactories(*Factories);
248 return Factories;
249 }();
250 Context.emplace(
251 std::make_unique<tidy::DefaultOptionsProvider>(
252 tidy::ClangTidyGlobalOptions(), std::move(*Options)),
253 /*AllowEnablingAnalyzerAlphaCheckers=*/false,
254 /*EnableModuleHeadersParsing=*/false,
256 Options.reset();
257 // The lifetime of DiagnosticOptions is managed by \c Clang.
258 Context->setDiagnosticsEngine(nullptr, &CI.getDiagnostics());
259 Context->setASTContext(&CI.getASTContext());
260 Context->setCurrentFile(Filename);
261 Context->setSelfContainedDiags(true);
262 tidy::ClangTidyCheckFactories Factories = *AllFactories;
263#if CLANG_TIDY_ENABLE_QUERY_BASED_CUSTOM_CHECKS
264 if (Context->canExperimentalCustomChecks() &&
266 // RegisterCustomChecks tracks names in process-wide mutable state.
267 // Serializing its use keeps concurrent AST builds independent.
268 static std::mutex CustomChecksMu;
269 std::lock_guard<std::mutex> Lock(CustomChecksMu);
270 tidy::custom::RegisterCustomChecks(Context->getOptions(), Factories);
271 }
272#endif
273 auto FastFactories = filterFastChecks(
274 Factories, Config::current().Diagnostics.ClangTidy.FastCheckFilter);
275 Checks = FastFactories.createChecksForLanguage(&*Context);
276
277 Preprocessor *PP = &CI.getPreprocessor();
278 for (const auto &Check : Checks) {
279 Check->registerPPCallbacks(CI.getSourceManager(), PP, PP);
280 Check->registerMatchers(&Finder);
281 }
282 }
283
284 void afterExecute(CompilerInstance &CI) override {
285 if (Checks.empty())
286 return;
287 // Run the AST-dependent part of the clang-tidy checks.
288 // (The preprocessor part ran already, via PPCallbacks).
289 trace::Span Tracer("ClangTidyMatch");
290 Finder.matchAST(CI.getASTContext());
291 }
292
293 void sawDiagnostic(const clang::Diagnostic &Info, clangd::Diag &D) override {
294 // Preserve the previous suppression policy when no checks are active.
295 if (Checks.empty())
296 return;
297 std::string CheckName = Context->getCheckName(Info.getID());
298 if (CheckName.empty())
299 return;
300
301 if (Config::current().Diagnostics.Suppress.contains(CheckName)) {
302 D.Severity = DiagnosticsEngine::Ignored;
303 return;
304 }
305
306 // Check for suppression comment. Skip the check for diagnostics not
307 // in the main file, because we don't want that function to query the
308 // source buffer for preamble files. For the same reason, we ask
309 // shouldSuppressDiagnostic to avoid I/O.
310 // We let suppression comments take precedence over warning-as-error
311 // to match clang-tidy's behaviour.
312 bool InsideMainFile =
313 Info.hasSourceManager() &&
314 isInsideMainFile(Info.getLocation(), Info.getSourceManager());
315 llvm::SmallVector<tooling::Diagnostic, 1> SuppressionErrors;
316 if (InsideMainFile &&
317 Context->shouldSuppressDiagnostic(D.Severity, Info, SuppressionErrors,
318 /*AllowIO=*/false,
319 /*EnableNolintBlocks=*/true)) {
320 // FIXME: should we expose the suppression error (invalid use of
321 // NOLINT comments)?
322 D.Severity = DiagnosticsEngine::Ignored;
323 return;
324 }
325 if (!Context->getOptions().SystemHeaders.value_or(false) &&
326 Info.hasSourceManager() &&
327 Info.getSourceManager().isInSystemMacro(Info.getLocation())) {
328 D.Severity = DiagnosticsEngine::Ignored;
329 return;
330 }
331 // Check for warning-as-error.
332 if (D.Severity == DiagnosticsEngine::Warning &&
333 Context->treatAsError(CheckName))
334 D.Severity = DiagnosticsEngine::Error;
335
336 // Compiler warnings keep their clang source and -W name, even when their
337 // severity or suppression is controlled by clang-tidy options.
338 if (!Context->isCompilerDiagnostic(Info.getID())) {
339 D.Name = std::move(CheckName);
340 D.Source = Diag::ClangTidy;
341 }
342 }
343
344 void finalizeDiagnostic(clangd::Diag &D) override {
345 // Constructor diagnostics precede check initialization.
346 if (D.Source == Diag::Unknown && Context &&
347 !Context->isCompilerDiagnostic(D.ID)) {
348 auto Name = Context->getCheckName(D.ID);
349 if (!Name.empty()) {
350 D.Name = std::move(Name);
351 D.Source = Diag::ClangTidy;
352 }
353 }
354 if (D.Source != Diag::ClangTidy)
355 return;
356 // clang-tidy bakes the name into diagnostic messages. Strip it out.
357 // It would be much nicer to make clang-tidy not do this.
358 auto CleanMessage = [&](std::string &Message) {
359 llvm::StringRef Rest(Message);
360 if (Rest.consume_back("]") && Rest.consume_back(D.Name) &&
361 Rest.consume_back(" ["))
362 Message.resize(Rest.size());
363 };
364 CleanMessage(D.Message);
365 for (auto &Note : D.Notes)
366 CleanMessage(Note.Message);
367 for (auto &Fix : D.Fixes)
368 CleanMessage(Fix.Message);
369 if (llvm::StringRef(D.Name).starts_with("misc-unused-") &&
370 !llvm::is_contained(D.Tags, DiagnosticTag::Unnecessary))
371 D.Tags.push_back(DiagnosticTag::Unnecessary);
372 if (llvm::StringRef(D.Name).starts_with("modernize-") &&
373 !llvm::is_contained(D.Tags, DiagnosticTag::Deprecated))
374 D.Tags.push_back(DiagnosticTag::Deprecated);
375 }
376
377private:
378 std::shared_ptr<const TidyProvider> Provider;
379 std::string Filename;
380 std::optional<tidy::ClangTidyOptions> Options;
381 // Destruction order matters: Finder and checks refer to Context.
382 std::optional<tidy::ClangTidyContext> Context;
383 std::vector<std::unique_ptr<tidy::ClangTidyCheck>> Checks;
384 ast_matchers::MatchFinder Finder;
385};
386
387} // namespace
388
389std::unique_ptr<FeatureModule::ASTListener>
391 std::lock_guard<std::mutex> Lock(ProviderMu);
392 if (!Provider)
393 return nullptr;
394 return std::make_unique<TidyASTListener>(Provider);
395}
396
397void ClangTidyFeatureModule::setProvider(TidyProvider OptionsProvider) {
398 std::shared_ptr<const TidyProvider> Replacement;
399 if (OptionsProvider)
400 Replacement =
401 std::make_shared<const TidyProvider>(std::move(OptionsProvider));
402 swapProvider(Replacement);
403}
404
406 std::shared_ptr<const TidyProvider> &OptionsProvider) {
407 std::lock_guard<std::mutex> Lock(ProviderMu);
408 Provider.swap(OptionsProvider);
409}
410
411} // namespace clangd
412} // namespace clang
static cl::opt< bool > Fix("fix", desc(R"( Apply suggested fixes. Without -fix-errors clang-tidy will bail out if any compilation errors were found. )"), cl::init(false), 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))
#define dlog(...)
Definition Logger.h:101
void swapProvider(std::shared_ptr< const TidyProvider > &OptionsProvider)
Swap provider snapshots, e.g.
std::unique_ptr< ASTListener > astListeners() override
Can be called asynchronously before building an AST.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:44
llvm::unique_function< void(tidy::ClangTidyOptions &, llvm::StringRef) const > TidyProvider
A factory to modify a tidy::ClangTidyOptions.
@ Info
An information message.
Definition Protocol.h:755
bool isInsideMainFile(SourceLocation Loc, const SourceManager &SM)
Returns true iff Loc is inside the main file.
std::optional< bool > isFastTidyCheck(llvm::StringRef Check)
Returns if Check is known-fast, known-slow, or its speed is unknown.
tidy::ClangTidyOptions getTidyOptionsForFile(TidyProviderRef Provider, llvm::StringRef Filename)
std::string Path
A typedef to represent a file path.
Definition Path.h:26
@ Deprecated
Deprecated or obsolete code.
Definition Protocol.h:936
@ Unnecessary
Unused or unnecessary code.
Definition Protocol.h:932
void elog(const char *Fmt, Ts &&... Vals)
Definition Logger.h:61
void(* RegisterCustomChecks)(const ClangTidyOptions &O, ClangTidyCheckFactories &Factories)
std::string configurationAsText(const ClangTidyOptions &Options)
Serializes configuration to a YAML-encoded string.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
FastCheckPolicy FastCheckFilter
Definition Config.h:113
static const Config & current()
Returns the Config of the current Context, or an empty configuration.
Definition Config.cpp:17
llvm::StringSet Suppress
Definition Config.h:106
struct clang::clangd::Config::@343034053122374337352226322054223376344037116252::@107156241027253143221327255130274177352007274355 ClangTidy
Configures what clang-tidy checks to run and options to use with them.
bool ExperimentalCustomChecks
Definition Config.h:114
Extension point that allows modules to observe and modify an AST build.