1//===--- tools/extra/clang-tidy/ClangTidyMain.cpp - Clang tidy tool -------===//
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/// \file This file implements a clang-tidy tool.
10///
11/// This tool uses the Clang Tooling infrastructure, see
12/// http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
13/// for details on setting it up with LLVM source tree.
14///
15//===----------------------------------------------------------------------===//
16
17#include "ClangTidyMain.h"
18#include "../ClangTidy.h"
19#include "../ClangTidyForceLinker.h"
20#include "../GlobList.h"
21#include "clang/Tooling/CommonOptionsParser.h"
22#include "llvm/Support/InitLLVM.h"
23#include "llvm/Support/Process.h"
24#include "llvm/Support/Signals.h"
25#include "llvm/Support/TargetSelect.h"
26#include "llvm/Support/WithColor.h"
27
28using namespace clang::tooling;
29using namespace llvm;
30
31static cl::OptionCategory ClangTidyCategory("clang-tidy options");
32
33static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);
34static cl::extrahelp ClangTidyHelp(R"(
35Configuration files:
36 clang-tidy attempts to read configuration for each source file from a
37 .clang-tidy file located in the closest parent directory of the source
38 file. If InheritParentConfig is true in a config file, the configuration file
39 in the parent directory (if any exists) will be taken and current config file
40 will be applied on top of the parent one. If any configuration options have
41 a corresponding command-line option, command-line option takes precedence.
42 The effective configuration can be inspected using -dump-config:
43
44 $ clang-tidy -dump-config
45 ---
46 Checks: '-*,some-check'
47 WarningsAsErrors: ''
48 HeaderFilterRegex: ''
49 FormatStyle: none
50 InheritParentConfig: true
51 User: user
52 CheckOptions:
53 - key: some-check.SomeOption
54 value: 'some value'
55 ...
56
57)");
58
59const char DefaultChecks[] = // Enable these checks by default:
60 "clang-diagnostic-*," // * compiler diagnostics
61 "clang-analyzer-*"; // * Static Analyzer checks
62
63static cl::opt<std::string> Checks("checks", cl::desc(R"(
64Comma-separated list of globs with optional '-'
65prefix. Globs are processed in order of
66appearance in the list. Globs without '-'
67prefix add checks with matching names to the
68set, globs with the '-' prefix remove checks
69with matching names from the set of enabled
70checks. This option's value is appended to the
71value of the 'Checks' option in .clang-tidy
72file, if any.
73)"),
74 cl::init(""), cl::cat(ClangTidyCategory));
75
76static cl::opt<std::string> WarningsAsErrors("warnings-as-errors", cl::desc(R"(
77Upgrades warnings to errors. Same format as
78'-checks'.
79This option's value is appended to the value of
80the 'WarningsAsErrors' option in .clang-tidy
81file, if any.
82)"),
83 cl::init(""),
84 cl::cat(ClangTidyCategory));
85
86static cl::opt<std::string> HeaderFilter("header-filter", cl::desc(R"(
87Regular expression matching the names of the
88headers to output diagnostics from. Diagnostics
89from the main file of each translation unit are
90always displayed.
91Can be used together with -line-filter.
92This option overrides the 'HeaderFilterRegex'
93option in .clang-tidy file, if any.
94)"),
95 cl::init(""),
96 cl::cat(ClangTidyCategory));
97
98static cl::opt<bool>
99 SystemHeaders("system-headers",
100 cl::desc("Display the errors from system headers."),
101 cl::init(false), cl::cat(ClangTidyCategory));
102static cl::opt<std::string> LineFilter("line-filter", cl::desc(R"(
103List of files with line ranges to filter the
104warnings. Can be used together with
105-header-filter. The format of the list is a
106JSON array of objects:
107 [
108 {"name":"file1.cpp","lines":[[1,3],[5,7]]},
109 {"name":"file2.h"}
110 ]
111)"),
112 cl::init(""),
113 cl::cat(ClangTidyCategory));
114
115static cl::opt<bool> Fix("fix", cl::desc(R"(
116Apply suggested fixes. Without -fix-errors
117clang-tidy will bail out if any compilation
118errors were found.
119)"),
120 cl::init(false), cl::cat(ClangTidyCategory));
121
122static cl::opt<bool> FixErrors("fix-errors", cl::desc(R"(
123Apply suggested fixes even if compilation
124errors were found. If compiler errors have
125attached fix-its, clang-tidy will apply them as
126well.
127)"),
128 cl::init(false), cl::cat(ClangTidyCategory));
129
130static cl::opt<bool> FixNotes("fix-notes", cl::desc(R"(
131If a warning has no fix, but a single fix can
132be found through an associated diagnostic note,
133apply the fix.
134Specifying this flag will implicitly enable the
135'--fix' flag.
136)"),
137 cl::init(false), cl::cat(ClangTidyCategory));
138
139static cl::opt<std::string> FormatStyle("format-style", cl::desc(R"(
140Style for formatting code around applied fixes:
141 - 'none' (default) turns off formatting
142 - 'file' (literally 'file', not a placeholder)
143 uses .clang-format file in the closest parent
144 directory
145 - '{ <json> }' specifies options inline, e.g.
146 -format-style='{BasedOnStyle: llvm, IndentWidth: 8}'
147 - 'llvm', 'google', 'webkit', 'mozilla'
148See clang-format documentation for the up-to-date
149information about formatting styles and options.
150This option overrides the 'FormatStyle` option in
151.clang-tidy file, if any.
152)"),
153 cl::init("none"),
154 cl::cat(ClangTidyCategory));
155
156static cl::opt<bool> ListChecks("list-checks", cl::desc(R"(
157List all enabled checks and exit. Use with
158-checks=* to list all available checks.
159)"),
160 cl::init(false), cl::cat(ClangTidyCategory));
161
162static cl::opt<bool> ExplainConfig("explain-config", cl::desc(R"(
163For each enabled check explains, where it is
164enabled, i.e. in clang-tidy binary, command
165line or a specific configuration file.
166)"),
167 cl::init(false), cl::cat(ClangTidyCategory));
168
169static cl::opt<std::string> Config("config", cl::desc(R"(
170Specifies a configuration in YAML/JSON format:
171 -config="{Checks: '*',
172 CheckOptions: [{key: x,
173 value: y}]}"
174When the value is empty, clang-tidy will
175attempt to find a file named .clang-tidy for
176each source file in its parent directories.
177)"),
178 cl::init(""), cl::cat(ClangTidyCategory));
179
180static cl::opt<std::string> ConfigFile("config-file", cl::desc(R"(
181Specify the path of .clang-tidy or custom config file:
182 e.g. --config-file=/some/path/myTidyConfigFile
183This option internally works exactly the same way as
184 --config option after reading specified config file.
185Use either --config-file or --config, not both.
186)"),
187 cl::init(""),
188 cl::cat(ClangTidyCategory));
189
190static cl::opt<bool> DumpConfig("dump-config", cl::desc(R"(
191Dumps configuration in the YAML format to
192stdout. This option can be used along with a
193file name (and '--' if the file is outside of a
194project with configured compilation database).
195The configuration used for this file will be
196printed.
197Use along with -checks=* to include
198configuration of all checks.
199)"),
200 cl::init(false), cl::cat(ClangTidyCategory));
201
202static cl::opt<bool> EnableCheckProfile("enable-check-profile", cl::desc(R"(
203Enable per-check timing profiles, and print a
204report to stderr.
205)"),
206 cl::init(false),
207 cl::cat(ClangTidyCategory));
208
209static cl::opt<std::string> StoreCheckProfile("store-check-profile",
210 cl::desc(R"(
211By default reports are printed in tabulated
212format to stderr. When this option is passed,
213these per-TU profiles are instead stored as JSON.
214)"),
215 cl::value_desc("prefix"),
216 cl::cat(ClangTidyCategory));
217
218/// This option allows enabling the experimental alpha checkers from the static
219/// analyzer. This option is set to false and not visible in help, because it is
220/// highly not recommended for users.
221static cl::opt<bool>
222 AllowEnablingAnalyzerAlphaCheckers("allow-enabling-analyzer-alpha-checkers",
223 cl::init(false), cl::Hidden,
224 cl::cat(ClangTidyCategory));
225
226static cl::opt<std::string> ExportFixes("export-fixes", cl::desc(R"(
227YAML file to store suggested fixes in. The
228stored fixes can be applied to the input source
229code with clang-apply-replacements.
230)"),
231 cl::value_desc("filename"),
232 cl::cat(ClangTidyCategory));
233
234static cl::opt<bool> Quiet("quiet", cl::desc(R"(
235Run clang-tidy in quiet mode. This suppresses
236printing statistics about ignored warnings and
237warnings treated as errors if the respective
238options are specified.
239)"),
240 cl::init(false),
241 cl::cat(ClangTidyCategory));
242
243static cl::opt<std::string> VfsOverlay("vfsoverlay", cl::desc(R"(
244Overlay the virtual filesystem described by file
245over the real file system.
246)"),
247 cl::value_desc("filename"),
248 cl::cat(ClangTidyCategory));
249
250static cl::opt<bool> UseColor("use-color", cl::desc(R"(
251Use colors in diagnostics. If not set, colors
252will be used if the terminal connected to
253standard output supports colors.
254This option overrides the 'UseColor' option in
255.clang-tidy file, if any.
256)"),
257 cl::init(false), cl::cat(ClangTidyCategory));
258
259namespace clang {
260namespace tidy {
261
262static void printStats(const ClangTidyStats &Stats) {
263 if (Stats.errorsIgnored()) {
264 llvm::errs() << "Suppressed " << Stats.errorsIgnored() << " warnings (";
265 StringRef Separator = "";
266 if (Stats.ErrorsIgnoredNonUserCode) {
267 llvm::errs() << Stats.ErrorsIgnoredNonUserCode << " in non-user code";
268 Separator = ", ";
269 }
270 if (Stats.ErrorsIgnoredLineFilter) {
271 llvm::errs() << Separator << Stats.ErrorsIgnoredLineFilter
272 << " due to line filter";
273 Separator = ", ";
274 }
275 if (Stats.ErrorsIgnoredNOLINT) {
276 llvm::errs() << Separator << Stats.ErrorsIgnoredNOLINT << " NOLINT";
277 Separator = ", ";
278 }
279 if (Stats.ErrorsIgnoredCheckFilter)
280 llvm::errs() << Separator << Stats.ErrorsIgnoredCheckFilter
281 << " with check filters";
282 llvm::errs() << ").\n";
283 if (Stats.ErrorsIgnoredNonUserCode)
284 llvm::errs() << "Use -header-filter=.* to display errors from all "
285 "non-system headers. Use -system-headers to display "
286 "errors from system headers as well.\n";
287 }
288}
289
290static std::unique_ptr<ClangTidyOptionsProvider> createOptionsProvider(
291 llvm::IntrusiveRefCntPtr<vfs::FileSystem> FS) {
292 ClangTidyGlobalOptions GlobalOptions;
293 if (std::error_code Err = parseLineFilter(LineFilter, GlobalOptions)) {
294 llvm::errs() << "Invalid LineFilter: " << Err.message() << "\n\nUsage:\n";
295 llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true);
296 return nullptr;
297 }
298
299 ClangTidyOptions DefaultOptions;
300 DefaultOptions.Checks = DefaultChecks;
301 DefaultOptions.WarningsAsErrors = "";
302 DefaultOptions.HeaderFilterRegex = HeaderFilter;
303 DefaultOptions.SystemHeaders = SystemHeaders;
304 DefaultOptions.FormatStyle = FormatStyle;
305 DefaultOptions.User = llvm::sys::Process::GetEnv("USER");
306 // USERNAME is used on Windows.
307 if (!DefaultOptions.User)
308 DefaultOptions.User = llvm::sys::Process::GetEnv("USERNAME");
309
310 ClangTidyOptions OverrideOptions;
311 if (Checks.getNumOccurrences() > 0)
312 OverrideOptions.Checks = Checks;
313 if (WarningsAsErrors.getNumOccurrences() > 0)
314 OverrideOptions.WarningsAsErrors = WarningsAsErrors;
315 if (HeaderFilter.getNumOccurrences() > 0)
316 OverrideOptions.HeaderFilterRegex = HeaderFilter;
317 if (SystemHeaders.getNumOccurrences() > 0)
318 OverrideOptions.SystemHeaders = SystemHeaders;
319 if (FormatStyle.getNumOccurrences() > 0)
320 OverrideOptions.FormatStyle = FormatStyle;
321 if (UseColor.getNumOccurrences() > 0)
322 OverrideOptions.UseColor = UseColor;
323
324 auto LoadConfig =
325 [&](StringRef Configuration,
326 StringRef Source) -> std::unique_ptr<ClangTidyOptionsProvider> {
327 llvm::ErrorOr<ClangTidyOptions> ParsedConfig =
328 parseConfiguration(MemoryBufferRef(Configuration, Source));
329 if (ParsedConfig)
330 return std::make_unique<ConfigOptionsProvider>(
331 std::move(GlobalOptions),
332 ClangTidyOptions::getDefaults().merge(DefaultOptions, 0),
333 std::move(*ParsedConfig), std::move(OverrideOptions), std::move(FS));
334 llvm::errs() << "Error: invalid configuration specified.\n"
335 << ParsedConfig.getError().message() << "\n";
336 return nullptr;
337 };
338
339 if (ConfigFile.getNumOccurrences() > 0) {
340 if (Config.getNumOccurrences() > 0) {
341 llvm::errs() << "Error: --config-file and --config are "
342 "mutually exclusive. Specify only one.\n";
343 return nullptr;
344 }
345
346 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
347 llvm::MemoryBuffer::getFile(ConfigFile);
348 if (std::error_code EC = Text.getError()) {
349 llvm::errs() << "Error: can't read config-file '" << ConfigFile
350 << "': " << EC.message() << "\n";
351 return nullptr;
352 }
353
354 return LoadConfig((*Text)->getBuffer(), ConfigFile);
355 }
356
357 if (Config.getNumOccurrences() > 0)
358 return LoadConfig(Config, "<command-line-config>");
359
360 return std::make_unique<FileOptionsProvider>(
361 std::move(GlobalOptions), std::move(DefaultOptions),
362 std::move(OverrideOptions), std::move(FS));
363}
364
365llvm::IntrusiveRefCntPtr<vfs::FileSystem>
366getVfsFromFile(const std::string &OverlayFile,
367 llvm::IntrusiveRefCntPtr<vfs::FileSystem> BaseFS) {
368 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Buffer =
369 BaseFS->getBufferForFile(OverlayFile);
370 if (!Buffer) {
371 llvm::errs() << "Can't load virtual filesystem overlay file '"
372 << OverlayFile << "': " << Buffer.getError().message()
373 << ".\n";
374 return nullptr;
375 }
376
377 IntrusiveRefCntPtr<vfs::FileSystem> FS = vfs::getVFSFromYAML(
378 std::move(Buffer.get()), /*DiagHandler*/ nullptr, OverlayFile);
379 if (!FS) {
380 llvm::errs() << "Error: invalid virtual filesystem overlay file '"
381 << OverlayFile << "'.\n";
382 return nullptr;
383 }
384 return FS;
385}
386
387int clangTidyMain(int argc, const char **argv) {
388 llvm::InitLLVM X(argc, argv);
389 llvm::Expected<CommonOptionsParser> OptionsParser =
390 CommonOptionsParser::create(argc, argv, ClangTidyCategory,
391 cl::ZeroOrMore);
392 if (!OptionsParser) {
393 llvm::WithColor::error() << llvm::toString(OptionsParser.takeError());
394 return 1;
395 }
396
397 llvm::IntrusiveRefCntPtr<vfs::OverlayFileSystem> BaseFS(
398 new vfs::OverlayFileSystem(vfs::getRealFileSystem()));
399
400 if (!VfsOverlay.empty()) {
401 IntrusiveRefCntPtr<vfs::FileSystem> VfsFromFile =
402 getVfsFromFile(VfsOverlay, BaseFS);
403 if (!VfsFromFile)
404 return 1;
405 BaseFS->pushOverlay(std::move(VfsFromFile));
406 }
407
408 auto OwningOptionsProvider = createOptionsProvider(BaseFS);
409 auto *OptionsProvider = OwningOptionsProvider.get();
410 if (!OptionsProvider)
411 return 1;
412
413 auto MakeAbsolute = [](const std::string &Input) -> SmallString<256> {
414 if (Input.empty())
415 return {};
416 SmallString<256> AbsolutePath(Input);
417 if (std::error_code EC = llvm::sys::fs::make_absolute(AbsolutePath)) {
418 llvm::errs() << "Can't make absolute path from " << Input << ": "
419 << EC.message() << "\n";
420 }
421 return AbsolutePath;
422 };
423
424 SmallString<256> ProfilePrefix = MakeAbsolute(StoreCheckProfile);
425
426 StringRef FileName("dummy");
427 auto PathList = OptionsParser->getSourcePathList();
428 if (!PathList.empty()) {
429 FileName = PathList.front();
430 }
431
432 SmallString<256> FilePath = MakeAbsolute(std::string(FileName));
433
434 ClangTidyOptions EffectiveOptions = OptionsProvider->getOptions(FilePath);
435 std::vector<std::string> EnabledChecks =
436 getCheckNames(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers);
437
438 if (ExplainConfig) {
439 // FIXME: Show other ClangTidyOptions' fields, like ExtraArg.
440 std::vector<clang::tidy::ClangTidyOptionsProvider::OptionsSource>
441 RawOptions = OptionsProvider->getRawOptions(FilePath);
442 for (const std::string &Check : EnabledChecks) {
443 for (auto It = RawOptions.rbegin(); It != RawOptions.rend(); ++It) {
444 if (It->first.Checks && GlobList(*It->first.Checks).contains(Check)) {
445 llvm::outs() << "'" << Check << "' is enabled in the " << It->second
446 << ".\n";
447 break;
448 }
449 }
450 }
451 return 0;
452 }
453
454 if (ListChecks) {
455 if (EnabledChecks.empty()) {
456 llvm::errs() << "No checks enabled.\n";
457 return 1;
458 }
459 llvm::outs() << "Enabled checks:";
460 for (const auto &CheckName : EnabledChecks)
461 llvm::outs() << "\n " << CheckName;
462 llvm::outs() << "\n\n";
463 return 0;
464 }
465
466 if (DumpConfig) {
467 EffectiveOptions.CheckOptions =
468 getCheckOptions(EffectiveOptions, AllowEnablingAnalyzerAlphaCheckers);
469 llvm::outs() << configurationAsText(ClangTidyOptions::getDefaults().merge(
470 EffectiveOptions, 0))
471 << "\n";
472 return 0;
473 }
474
475 if (EnabledChecks.empty()) {
476 llvm::errs() << "Error: no checks enabled.\n";
477 llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true);
478 return 1;
479 }
480
481 if (PathList.empty()) {
482 llvm::errs() << "Error: no input files specified.\n";
483 llvm::cl::PrintHelpMessage(/*Hidden=*/false, /*Categorized=*/true);
484 return 1;
485 }
486
487 llvm::InitializeAllTargetInfos();
488 llvm::InitializeAllTargetMCs();
489 llvm::InitializeAllAsmParsers();
490
491 ClangTidyContext Context(std::move(OwningOptionsProvider),
492 AllowEnablingAnalyzerAlphaCheckers);
493 std::vector<ClangTidyError> Errors =
494 runClangTidy(Context, OptionsParser->getCompilations(), PathList, BaseFS,
495 FixNotes, EnableCheckProfile, ProfilePrefix);
496 bool FoundErrors = llvm::find_if(Errors, [](const ClangTidyError &E) {
497 return E.DiagLevel == ClangTidyError::Error;
498 }) != Errors.end();
499
500 // --fix-errors and --fix-notes imply --fix.
501 FixBehaviour Behaviour = FixNotes ? FB_FixNotes
502 : (Fix || FixErrors) ? FB_Fix
503 : FB_NoFix;
504
505 const bool DisableFixes = FoundErrors && !FixErrors;
506
507 unsigned WErrorCount = 0;
508
509 handleErrors(Errors, Context, DisableFixes ? FB_NoFix : Behaviour,
510 WErrorCount, BaseFS);
511
512 if (!ExportFixes.empty() && !Errors.empty()) {
513 std::error_code EC;
514 llvm::raw_fd_ostream OS(ExportFixes, EC, llvm::sys::fs::OF_None);
515 if (EC) {
516 llvm::errs() << "Error opening output file: " << EC.message() << '\n';
517 return 1;
518 }
519 exportReplacements(FilePath.str(), Errors, OS);
520 }
521
522 if (!Quiet) {
523 printStats(Context.getStats());
524 if (DisableFixes && Behaviour != FB_NoFix)
525 llvm::errs()
526 << "Found compiler errors, but -fix-errors was not specified.\n"
527 "Fixes have NOT been applied.\n\n";
528 }
529
530 if (WErrorCount) {
531 if (!Quiet) {
532 StringRef Plural = WErrorCount == 1 ? "" : "s";
533 llvm::errs() << WErrorCount << " warning" << Plural << " treated as error"
534 << Plural << "\n";
535 }
536 return 1;
537 }
538
539 if (FoundErrors) {
540 // TODO: Figure out when zero exit code should be used with -fix-errors:
541 // a. when a fix has been applied for an error
542 // b. when a fix has been applied for all errors
543 // c. some other condition.
544 // For now always returning zero when -fix-errors is used.
545 if (FixErrors)
546 return 0;
547 if (!Quiet)
548 llvm::errs() << "Found compiler error(s).\n";
549 return 1;
550 }
551
552 return 0;
553}
554
555} // namespace tidy
556} // namespace clang
557