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