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