1 //===--- ClangTidyOptions.cpp - clang-tidy ----------------------*- C++ -*-===//
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 "ClangTidyOptions.h"
10 #include "ClangTidyModuleRegistry.h"
11 #include "clang/Basic/LLVM.h"
12 #include "llvm/ADT/SmallString.h"
13 #include "llvm/Support/Debug.h"
14 #include "llvm/Support/Errc.h"
15 #include "llvm/Support/FileSystem.h"
16 #include "llvm/Support/MemoryBufferRef.h"
17 #include "llvm/Support/Path.h"
18 #include "llvm/Support/YAMLTraits.h"
19 #include <utility>
20 
21 #define DEBUG_TYPE "clang-tidy-options"
22 
23 using clang::tidy::ClangTidyOptions;
24 using clang::tidy::FileFilter;
25 using OptionsSource = clang::tidy::ClangTidyOptionsProvider::OptionsSource;
26 
27 LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter)
28 LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FileFilter::LineRange)
29 
30 namespace llvm {
31 namespace yaml {
32 
33 // Map std::pair<int, int> to a JSON array of size 2.
34 template <> struct SequenceTraits<FileFilter::LineRange> {
sizellvm::yaml::SequenceTraits35   static size_t size(IO &IO, FileFilter::LineRange &Range) {
36     return Range.first == 0 ? 0 : Range.second == 0 ? 1 : 2;
37   }
elementllvm::yaml::SequenceTraits38   static unsigned &element(IO &IO, FileFilter::LineRange &Range, size_t Index) {
39     if (Index > 1)
40       IO.setError("Too many elements in line range.");
41     return Index == 0 ? Range.first : Range.second;
42   }
43 };
44 
45 template <> struct MappingTraits<FileFilter> {
mappingllvm::yaml::MappingTraits46   static void mapping(IO &IO, FileFilter &File) {
47     IO.mapRequired("name", File.Name);
48     IO.mapOptional("lines", File.LineRanges);
49   }
validatellvm::yaml::MappingTraits50   static std::string validate(IO &Io, FileFilter &File) {
51     if (File.Name.empty())
52       return "No file name specified";
53     for (const FileFilter::LineRange &Range : File.LineRanges) {
54       if (Range.first <= 0 || Range.second <= 0)
55         return "Invalid line range";
56     }
57     return "";
58   }
59 };
60 
61 template <> struct MappingTraits<ClangTidyOptions::StringPair> {
mappingllvm::yaml::MappingTraits62   static void mapping(IO &IO, ClangTidyOptions::StringPair &KeyValue) {
63     IO.mapRequired("key", KeyValue.first);
64     IO.mapRequired("value", KeyValue.second);
65   }
66 };
67 
68 struct NOptionMap {
NOptionMapllvm::yaml::NOptionMap69   NOptionMap(IO &) {}
NOptionMapllvm::yaml::NOptionMap70   NOptionMap(IO &, const ClangTidyOptions::OptionMap &OptionMap) {
71     Options.reserve(OptionMap.size());
72     for (const auto &KeyValue : OptionMap)
73       Options.emplace_back(std::string(KeyValue.getKey()), KeyValue.getValue().Value);
74   }
denormalizellvm::yaml::NOptionMap75   ClangTidyOptions::OptionMap denormalize(IO &) {
76     ClangTidyOptions::OptionMap Map;
77     for (const auto &KeyValue : Options)
78       Map[KeyValue.first] = ClangTidyOptions::ClangTidyValue(KeyValue.second);
79     return Map;
80   }
81   std::vector<ClangTidyOptions::StringPair> Options;
82 };
83 
84 template <>
yamlize(IO & IO,ClangTidyOptions::OptionMap & Options,bool,EmptyContext & Ctx)85 void yamlize(IO &IO, ClangTidyOptions::OptionMap &Options, bool,
86              EmptyContext &Ctx) {
87   if (IO.outputting()) {
88     IO.beginMapping();
89     // Only output as a map
90     for (auto &Key : Options) {
91       bool UseDefault;
92       void *SaveInfo;
93       IO.preflightKey(Key.getKey().data(), true, false, UseDefault, SaveInfo);
94       StringRef S = Key.getValue().Value;
95       IO.scalarString(S, needsQuotes(S));
96       IO.postflightKey(SaveInfo);
97     }
98     IO.endMapping();
99   } else {
100     // We need custom logic here to support the old method of specifying check
101     // options using a list of maps containing key and value keys.
102     Input &I = reinterpret_cast<Input &>(IO);
103     if (isa<SequenceNode>(I.getCurrentNode())) {
104       MappingNormalization<NOptionMap, ClangTidyOptions::OptionMap> NOpts(
105           IO, Options);
106       EmptyContext Ctx;
107       yamlize(IO, NOpts->Options, true, Ctx);
108     } else if (isa<MappingNode>(I.getCurrentNode())) {
109       IO.beginMapping();
110       for (StringRef Key : IO.keys()) {
111         IO.mapRequired(Key.data(), Options[Key].Value);
112       }
113       IO.endMapping();
114     } else {
115       IO.setError("expected a sequence or map");
116     }
117   }
118 }
119 
120 template <> struct MappingTraits<ClangTidyOptions> {
mappingllvm::yaml::MappingTraits121   static void mapping(IO &IO, ClangTidyOptions &Options) {
122     bool Ignored = false;
123     IO.mapOptional("Checks", Options.Checks);
124     IO.mapOptional("WarningsAsErrors", Options.WarningsAsErrors);
125     IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex);
126     IO.mapOptional("AnalyzeTemporaryDtors", Ignored); // legacy compatibility
127     IO.mapOptional("FormatStyle", Options.FormatStyle);
128     IO.mapOptional("User", Options.User);
129     IO.mapOptional("CheckOptions", Options.CheckOptions);
130     IO.mapOptional("ExtraArgs", Options.ExtraArgs);
131     IO.mapOptional("ExtraArgsBefore", Options.ExtraArgsBefore);
132     IO.mapOptional("InheritParentConfig", Options.InheritParentConfig);
133     IO.mapOptional("UseColor", Options.UseColor);
134   }
135 };
136 
137 } // namespace yaml
138 } // namespace llvm
139 
140 namespace clang {
141 namespace tidy {
142 
getDefaults()143 ClangTidyOptions ClangTidyOptions::getDefaults() {
144   ClangTidyOptions Options;
145   Options.Checks = "";
146   Options.WarningsAsErrors = "";
147   Options.HeaderFilterRegex = "";
148   Options.SystemHeaders = false;
149   Options.FormatStyle = "none";
150   Options.User = llvm::None;
151   for (const ClangTidyModuleRegistry::entry &Module :
152        ClangTidyModuleRegistry::entries())
153     Options.mergeWith(Module.instantiate()->getModuleOptions(), 0);
154   return Options;
155 }
156 
157 template <typename T>
mergeVectors(Optional<T> & Dest,const Optional<T> & Src)158 static void mergeVectors(Optional<T> &Dest, const Optional<T> &Src) {
159   if (Src) {
160     if (Dest)
161       Dest->insert(Dest->end(), Src->begin(), Src->end());
162     else
163       Dest = Src;
164   }
165 }
166 
mergeCommaSeparatedLists(Optional<std::string> & Dest,const Optional<std::string> & Src)167 static void mergeCommaSeparatedLists(Optional<std::string> &Dest,
168                                      const Optional<std::string> &Src) {
169   if (Src)
170     Dest = (Dest && !Dest->empty() ? *Dest + "," : "") + *Src;
171 }
172 
173 template <typename T>
overrideValue(Optional<T> & Dest,const Optional<T> & Src)174 static void overrideValue(Optional<T> &Dest, const Optional<T> &Src) {
175   if (Src)
176     Dest = Src;
177 }
178 
mergeWith(const ClangTidyOptions & Other,unsigned Order)179 ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,
180                                               unsigned Order) {
181   mergeCommaSeparatedLists(Checks, Other.Checks);
182   mergeCommaSeparatedLists(WarningsAsErrors, Other.WarningsAsErrors);
183   overrideValue(HeaderFilterRegex, Other.HeaderFilterRegex);
184   overrideValue(SystemHeaders, Other.SystemHeaders);
185   overrideValue(FormatStyle, Other.FormatStyle);
186   overrideValue(User, Other.User);
187   overrideValue(UseColor, Other.UseColor);
188   mergeVectors(ExtraArgs, Other.ExtraArgs);
189   mergeVectors(ExtraArgsBefore, Other.ExtraArgsBefore);
190 
191   for (const auto &KeyValue : Other.CheckOptions) {
192     CheckOptions.insert_or_assign(
193         KeyValue.getKey(),
194         ClangTidyValue(KeyValue.getValue().Value,
195                        KeyValue.getValue().Priority + Order));
196   }
197   return *this;
198 }
199 
merge(const ClangTidyOptions & Other,unsigned Order) const200 ClangTidyOptions ClangTidyOptions::merge(const ClangTidyOptions &Other,
201                                          unsigned Order) const {
202   ClangTidyOptions Result = *this;
203   Result.mergeWith(Other, Order);
204   return Result;
205 }
206 
207 const char ClangTidyOptionsProvider::OptionsSourceTypeDefaultBinary[] =
208     "clang-tidy binary";
209 const char ClangTidyOptionsProvider::OptionsSourceTypeCheckCommandLineOption[] =
210     "command-line option '-checks'";
211 const char
212     ClangTidyOptionsProvider::OptionsSourceTypeConfigCommandLineOption[] =
213         "command-line option '-config'";
214 
215 ClangTidyOptions
getOptions(llvm::StringRef FileName)216 ClangTidyOptionsProvider::getOptions(llvm::StringRef FileName) {
217   ClangTidyOptions Result;
218   unsigned Priority = 0;
219   for (auto &Source : getRawOptions(FileName))
220     Result.mergeWith(Source.first, ++Priority);
221   return Result;
222 }
223 
224 std::vector<OptionsSource>
getRawOptions(llvm::StringRef FileName)225 DefaultOptionsProvider::getRawOptions(llvm::StringRef FileName) {
226   std::vector<OptionsSource> Result;
227   Result.emplace_back(DefaultOptions, OptionsSourceTypeDefaultBinary);
228   return Result;
229 }
230 
ConfigOptionsProvider(ClangTidyGlobalOptions GlobalOptions,ClangTidyOptions DefaultOptions,ClangTidyOptions ConfigOptions,ClangTidyOptions OverrideOptions,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)231 ConfigOptionsProvider::ConfigOptionsProvider(
232     ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
233     ClangTidyOptions ConfigOptions, ClangTidyOptions OverrideOptions,
234     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
235     : FileOptionsBaseProvider(std::move(GlobalOptions),
236                               std::move(DefaultOptions),
237                               std::move(OverrideOptions), std::move(FS)),
238       ConfigOptions(std::move(ConfigOptions)) {}
239 
240 std::vector<OptionsSource>
getRawOptions(llvm::StringRef FileName)241 ConfigOptionsProvider::getRawOptions(llvm::StringRef FileName) {
242   std::vector<OptionsSource> RawOptions =
243       DefaultOptionsProvider::getRawOptions(FileName);
244   if (ConfigOptions.InheritParentConfig.value_or(false)) {
245     LLVM_DEBUG(llvm::dbgs()
246                << "Getting options for file " << FileName << "...\n");
247     assert(FS && "FS must be set.");
248 
249     llvm::SmallString<128> AbsoluteFilePath(FileName);
250 
251     if (!FS->makeAbsolute(AbsoluteFilePath)) {
252       addRawFileOptions(AbsoluteFilePath, RawOptions);
253     }
254   }
255   RawOptions.emplace_back(ConfigOptions,
256                           OptionsSourceTypeConfigCommandLineOption);
257   RawOptions.emplace_back(OverrideOptions,
258                           OptionsSourceTypeCheckCommandLineOption);
259   return RawOptions;
260 }
261 
FileOptionsBaseProvider(ClangTidyGlobalOptions GlobalOptions,ClangTidyOptions DefaultOptions,ClangTidyOptions OverrideOptions,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)262 FileOptionsBaseProvider::FileOptionsBaseProvider(
263     ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
264     ClangTidyOptions OverrideOptions,
265     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)
266     : DefaultOptionsProvider(std::move(GlobalOptions),
267                              std::move(DefaultOptions)),
268       OverrideOptions(std::move(OverrideOptions)), FS(std::move(VFS)) {
269   if (!FS)
270     FS = llvm::vfs::getRealFileSystem();
271   ConfigHandlers.emplace_back(".clang-tidy", parseConfiguration);
272 }
273 
FileOptionsBaseProvider(ClangTidyGlobalOptions GlobalOptions,ClangTidyOptions DefaultOptions,ClangTidyOptions OverrideOptions,FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers)274 FileOptionsBaseProvider::FileOptionsBaseProvider(
275     ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
276     ClangTidyOptions OverrideOptions,
277     FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers)
278     : DefaultOptionsProvider(std::move(GlobalOptions),
279                              std::move(DefaultOptions)),
280       OverrideOptions(std::move(OverrideOptions)),
281       ConfigHandlers(std::move(ConfigHandlers)) {}
282 
addRawFileOptions(llvm::StringRef AbsolutePath,std::vector<OptionsSource> & CurOptions)283 void FileOptionsBaseProvider::addRawFileOptions(
284     llvm::StringRef AbsolutePath, std::vector<OptionsSource> &CurOptions) {
285   auto CurSize = CurOptions.size();
286 
287   // Look for a suitable configuration file in all parent directories of the
288   // file. Start with the immediate parent directory and move up.
289   StringRef Path = llvm::sys::path::parent_path(AbsolutePath);
290   for (StringRef CurrentPath = Path; !CurrentPath.empty();
291        CurrentPath = llvm::sys::path::parent_path(CurrentPath)) {
292     llvm::Optional<OptionsSource> Result;
293 
294     auto Iter = CachedOptions.find(CurrentPath);
295     if (Iter != CachedOptions.end())
296       Result = Iter->second;
297 
298     if (!Result)
299       Result = tryReadConfigFile(CurrentPath);
300 
301     if (Result) {
302       // Store cached value for all intermediate directories.
303       while (Path != CurrentPath) {
304         LLVM_DEBUG(llvm::dbgs()
305                    << "Caching configuration for path " << Path << ".\n");
306         if (!CachedOptions.count(Path))
307           CachedOptions[Path] = *Result;
308         Path = llvm::sys::path::parent_path(Path);
309       }
310       CachedOptions[Path] = *Result;
311 
312       CurOptions.push_back(*Result);
313       if (!Result->first.InheritParentConfig.value_or(false))
314         break;
315     }
316   }
317   // Reverse order of file configs because closer configs should have higher
318   // priority.
319   std::reverse(CurOptions.begin() + CurSize, CurOptions.end());
320 }
321 
FileOptionsProvider(ClangTidyGlobalOptions GlobalOptions,ClangTidyOptions DefaultOptions,ClangTidyOptions OverrideOptions,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)322 FileOptionsProvider::FileOptionsProvider(
323     ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
324     ClangTidyOptions OverrideOptions,
325     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)
326     : FileOptionsBaseProvider(std::move(GlobalOptions),
327                               std::move(DefaultOptions),
328                               std::move(OverrideOptions), std::move(VFS)) {}
329 
FileOptionsProvider(ClangTidyGlobalOptions GlobalOptions,ClangTidyOptions DefaultOptions,ClangTidyOptions OverrideOptions,FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers)330 FileOptionsProvider::FileOptionsProvider(
331     ClangTidyGlobalOptions GlobalOptions, ClangTidyOptions DefaultOptions,
332     ClangTidyOptions OverrideOptions,
333     FileOptionsBaseProvider::ConfigFileHandlers ConfigHandlers)
334     : FileOptionsBaseProvider(
335           std::move(GlobalOptions), std::move(DefaultOptions),
336           std::move(OverrideOptions), std::move(ConfigHandlers)) {}
337 
338 // FIXME: This method has some common logic with clang::format::getStyle().
339 // Consider pulling out common bits to a findParentFileWithName function or
340 // similar.
341 std::vector<OptionsSource>
getRawOptions(StringRef FileName)342 FileOptionsProvider::getRawOptions(StringRef FileName) {
343   LLVM_DEBUG(llvm::dbgs() << "Getting options for file " << FileName
344                           << "...\n");
345   assert(FS && "FS must be set.");
346 
347   llvm::SmallString<128> AbsoluteFilePath(FileName);
348 
349   if (FS->makeAbsolute(AbsoluteFilePath))
350     return {};
351 
352   std::vector<OptionsSource> RawOptions =
353       DefaultOptionsProvider::getRawOptions(AbsoluteFilePath.str());
354   addRawFileOptions(AbsoluteFilePath, RawOptions);
355   OptionsSource CommandLineOptions(OverrideOptions,
356                                    OptionsSourceTypeCheckCommandLineOption);
357 
358   RawOptions.push_back(CommandLineOptions);
359   return RawOptions;
360 }
361 
362 llvm::Optional<OptionsSource>
tryReadConfigFile(StringRef Directory)363 FileOptionsBaseProvider::tryReadConfigFile(StringRef Directory) {
364   assert(!Directory.empty());
365 
366   llvm::ErrorOr<llvm::vfs::Status> DirectoryStatus = FS->status(Directory);
367 
368   if (!DirectoryStatus || !DirectoryStatus->isDirectory()) {
369     llvm::errs() << "Error reading configuration from " << Directory
370                  << ": directory doesn't exist.\n";
371     return llvm::None;
372   }
373 
374   for (const ConfigFileHandler &ConfigHandler : ConfigHandlers) {
375     SmallString<128> ConfigFile(Directory);
376     llvm::sys::path::append(ConfigFile, ConfigHandler.first);
377     LLVM_DEBUG(llvm::dbgs() << "Trying " << ConfigFile << "...\n");
378 
379     llvm::ErrorOr<llvm::vfs::Status> FileStatus = FS->status(ConfigFile);
380 
381     if (!FileStatus || !FileStatus->isRegularFile())
382       continue;
383 
384     llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> Text =
385         FS->getBufferForFile(ConfigFile);
386     if (std::error_code EC = Text.getError()) {
387       llvm::errs() << "Can't read " << ConfigFile << ": " << EC.message()
388                    << "\n";
389       continue;
390     }
391 
392     // Skip empty files, e.g. files opened for writing via shell output
393     // redirection.
394     if ((*Text)->getBuffer().empty())
395       continue;
396     llvm::ErrorOr<ClangTidyOptions> ParsedOptions =
397         ConfigHandler.second({(*Text)->getBuffer(), ConfigFile});
398     if (!ParsedOptions) {
399       if (ParsedOptions.getError())
400         llvm::errs() << "Error parsing " << ConfigFile << ": "
401                      << ParsedOptions.getError().message() << "\n";
402       continue;
403     }
404     return OptionsSource(*ParsedOptions, std::string(ConfigFile));
405   }
406   return llvm::None;
407 }
408 
409 /// Parses -line-filter option and stores it to the \c Options.
parseLineFilter(StringRef LineFilter,clang::tidy::ClangTidyGlobalOptions & Options)410 std::error_code parseLineFilter(StringRef LineFilter,
411                                 clang::tidy::ClangTidyGlobalOptions &Options) {
412   llvm::yaml::Input Input(LineFilter);
413   Input >> Options.LineFilter;
414   return Input.error();
415 }
416 
417 llvm::ErrorOr<ClangTidyOptions>
parseConfiguration(llvm::MemoryBufferRef Config)418 parseConfiguration(llvm::MemoryBufferRef Config) {
419   llvm::yaml::Input Input(Config);
420   ClangTidyOptions Options;
421   Input >> Options;
422   if (Input.error())
423     return Input.error();
424   return Options;
425 }
426 
diagHandlerImpl(const llvm::SMDiagnostic & Diag,void * Ctx)427 static void diagHandlerImpl(const llvm::SMDiagnostic &Diag, void *Ctx) {
428   (*reinterpret_cast<DiagCallback *>(Ctx))(Diag);
429 }
430 
431 llvm::ErrorOr<ClangTidyOptions>
parseConfigurationWithDiags(llvm::MemoryBufferRef Config,DiagCallback Handler)432 parseConfigurationWithDiags(llvm::MemoryBufferRef Config,
433                             DiagCallback Handler) {
434   llvm::yaml::Input Input(Config, nullptr, Handler ? diagHandlerImpl : nullptr,
435                           &Handler);
436   ClangTidyOptions Options;
437   Input >> Options;
438   if (Input.error())
439     return Input.error();
440   return Options;
441 }
442 
configurationAsText(const ClangTidyOptions & Options)443 std::string configurationAsText(const ClangTidyOptions &Options) {
444   std::string Text;
445   llvm::raw_string_ostream Stream(Text);
446   llvm::yaml::Output Output(Stream);
447   // We use the same mapping method for input and output, so we need a non-const
448   // reference here.
449   ClangTidyOptions NonConstValue = Options;
450   Output << NonConstValue;
451   return Stream.str();
452 }
453 
454 } // namespace tidy
455 } // namespace clang
456