• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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