1 //===--- TidyProvider.cpp - create options for running clang-tidy----------===// 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 "TidyProvider.h" 10 #include "../clang-tidy/ClangTidyModuleRegistry.h" 11 #include "../clang-tidy/ClangTidyOptions.h" 12 #include "Config.h" 13 #include "support/FileCache.h" 14 #include "support/Logger.h" 15 #include "support/Path.h" 16 #include "support/ThreadsafeFS.h" 17 #include "llvm/ADT/STLExtras.h" 18 #include "llvm/ADT/SmallString.h" 19 #include "llvm/ADT/StringExtras.h" 20 #include "llvm/ADT/StringSet.h" 21 #include "llvm/Support/Allocator.h" 22 #include "llvm/Support/Process.h" 23 #include "llvm/Support/SourceMgr.h" 24 #include <memory> 25 #include <optional> 26 27 namespace clang { 28 namespace clangd { 29 namespace { 30 31 // Access to config from a .clang-tidy file, caching IO and parsing. 32 class DotClangTidyCache : private FileCache { 33 // We cache and expose shared_ptr to avoid copying the value on every lookup 34 // when we're ultimately just going to pass it to mergeWith. 35 mutable std::shared_ptr<const tidy::ClangTidyOptions> Value; 36 37 public: 38 DotClangTidyCache(PathRef Path) : FileCache(Path) {} 39 40 std::shared_ptr<const tidy::ClangTidyOptions> 41 get(const ThreadsafeFS &TFS, 42 std::chrono::steady_clock::time_point FreshTime) const { 43 std::shared_ptr<const tidy::ClangTidyOptions> Result; 44 read( 45 TFS, FreshTime, 46 [this](std::optional<llvm::StringRef> Data) { 47 Value.reset(); 48 if (Data && !Data->empty()) { 49 auto Diagnostics = [](const llvm::SMDiagnostic &D) { 50 switch (D.getKind()) { 51 case llvm::SourceMgr::DK_Error: 52 elog("tidy-config error at {0}:{1}:{2}: {3}", D.getFilename(), 53 D.getLineNo(), D.getColumnNo(), D.getMessage()); 54 break; 55 case llvm::SourceMgr::DK_Warning: 56 log("tidy-config warning at {0}:{1}:{2}: {3}", D.getFilename(), 57 D.getLineNo(), D.getColumnNo(), D.getMessage()); 58 break; 59 case llvm::SourceMgr::DK_Note: 60 case llvm::SourceMgr::DK_Remark: 61 vlog("tidy-config note at {0}:{1}:{2}: {3}", D.getFilename(), 62 D.getLineNo(), D.getColumnNo(), D.getMessage()); 63 break; 64 } 65 }; 66 if (auto Parsed = tidy::parseConfigurationWithDiags( 67 llvm::MemoryBufferRef(*Data, path()), Diagnostics)) 68 Value = std::make_shared<const tidy::ClangTidyOptions>( 69 std::move(*Parsed)); 70 else 71 elog("Error parsing clang-tidy configuration in {0}: {1}", path(), 72 Parsed.getError().message()); 73 } 74 }, 75 [&]() { Result = Value; }); 76 return Result; 77 } 78 }; 79 80 // Access to combined config from .clang-tidy files governing a source file. 81 // Each config file is cached and the caches are shared for affected sources. 82 // 83 // FIXME: largely duplicates config::Provider::fromAncestorRelativeYAMLFiles. 84 // Potentially useful for compile_commands.json too. Extract? 85 class DotClangTidyTree { 86 const ThreadsafeFS &FS; 87 std::string RelPath; 88 std::chrono::steady_clock::duration MaxStaleness; 89 90 mutable std::mutex Mu; 91 // Keys are the ancestor directory, not the actual config path within it. 92 // We only insert into this map, so pointers to values are stable forever. 93 // Mutex guards the map itself, not the values (which are threadsafe). 94 mutable llvm::StringMap<DotClangTidyCache> Cache; 95 96 public: 97 DotClangTidyTree(const ThreadsafeFS &FS) 98 : FS(FS), RelPath(".clang-tidy"), MaxStaleness(std::chrono::seconds(5)) {} 99 100 void apply(tidy::ClangTidyOptions &Result, PathRef AbsPath) { 101 namespace path = llvm::sys::path; 102 assert(path::is_absolute(AbsPath)); 103 104 // Compute absolute paths to all ancestors (substrings of P.Path). 105 // Ensure cache entries for each ancestor exist in the map. 106 llvm::SmallVector<DotClangTidyCache *> Caches; 107 { 108 std::lock_guard<std::mutex> Lock(Mu); 109 for (auto Ancestor = absoluteParent(AbsPath); !Ancestor.empty(); 110 Ancestor = absoluteParent(Ancestor)) { 111 auto It = Cache.find(Ancestor); 112 // Assemble the actual config file path only if needed. 113 if (It == Cache.end()) { 114 llvm::SmallString<256> ConfigPath = Ancestor; 115 path::append(ConfigPath, RelPath); 116 It = Cache.try_emplace(Ancestor, ConfigPath.str()).first; 117 } 118 Caches.push_back(&It->second); 119 } 120 } 121 // Finally query each individual file. 122 // This will take a (per-file) lock for each file that actually exists. 123 std::chrono::steady_clock::time_point FreshTime = 124 std::chrono::steady_clock::now() - MaxStaleness; 125 llvm::SmallVector<std::shared_ptr<const tidy::ClangTidyOptions>> 126 OptionStack; 127 for (const DotClangTidyCache *Cache : Caches) 128 if (auto Config = Cache->get(FS, FreshTime)) { 129 OptionStack.push_back(std::move(Config)); 130 if (!OptionStack.back()->InheritParentConfig.value_or(false)) 131 break; 132 } 133 unsigned Order = 1u; 134 for (auto &Option : llvm::reverse(OptionStack)) 135 Result.mergeWith(*Option, Order++); 136 } 137 }; 138 139 } // namespace 140 141 static void mergeCheckList(std::optional<std::string> &Checks, 142 llvm::StringRef List) { 143 if (List.empty()) 144 return; 145 if (!Checks || Checks->empty()) { 146 Checks.emplace(List); 147 return; 148 } 149 *Checks = llvm::join_items(",", *Checks, List); 150 } 151 152 TidyProvider provideEnvironment() { 153 static const std::optional<std::string> User = [] { 154 std::optional<std::string> Ret = llvm::sys::Process::GetEnv("USER"); 155 #ifdef _WIN32 156 if (!Ret) 157 return llvm::sys::Process::GetEnv("USERNAME"); 158 #endif 159 return Ret; 160 }(); 161 162 if (User) 163 return 164 [](tidy::ClangTidyOptions &Opts, llvm::StringRef) { Opts.User = User; }; 165 // FIXME: Once function_ref and unique_function operator= operators handle 166 // null values, this can return null. 167 return [](tidy::ClangTidyOptions &, llvm::StringRef) {}; 168 } 169 170 TidyProvider provideDefaultChecks() { 171 // These default checks are chosen for: 172 // - low false-positive rate 173 // - providing a lot of value 174 // - being reasonably efficient 175 static const std::string DefaultChecks = llvm::join_items( 176 ",", "readability-misleading-indentation", "readability-deleted-default", 177 "bugprone-integer-division", "bugprone-sizeof-expression", 178 "bugprone-suspicious-missing-comma", "bugprone-unused-raii", 179 "bugprone-unused-return-value", "misc-unused-using-decls", 180 "misc-unused-alias-decls", "misc-definitions-in-headers"); 181 return [](tidy::ClangTidyOptions &Opts, llvm::StringRef) { 182 if (!Opts.Checks || Opts.Checks->empty()) 183 Opts.Checks = DefaultChecks; 184 }; 185 } 186 187 TidyProvider addTidyChecks(llvm::StringRef Checks, 188 llvm::StringRef WarningsAsErrors) { 189 return [Checks = std::string(Checks), 190 WarningsAsErrors = std::string(WarningsAsErrors)]( 191 tidy::ClangTidyOptions &Opts, llvm::StringRef) { 192 mergeCheckList(Opts.Checks, Checks); 193 mergeCheckList(Opts.WarningsAsErrors, WarningsAsErrors); 194 }; 195 } 196 197 TidyProvider disableUnusableChecks(llvm::ArrayRef<std::string> ExtraBadChecks) { 198 constexpr llvm::StringLiteral Separator(","); 199 static const std::string BadChecks = llvm::join_items( 200 Separator, 201 // We want this list to start with a separator to 202 // simplify appending in the lambda. So including an 203 // empty string here will force that. 204 "", 205 // include-cleaner is directly integrated in IncludeCleaner.cpp 206 "-misc-include-cleaner", 207 208 // ----- False Positives ----- 209 210 // Check relies on seeing ifndef/define/endif directives, 211 // clangd doesn't replay those when using a preamble. 212 "-llvm-header-guard", "-modernize-macro-to-enum", 213 214 // ----- Crashing Checks ----- 215 216 // Check can choke on invalid (intermediate) c++ 217 // code, which is often the case when clangd 218 // tries to build an AST. 219 "-bugprone-use-after-move", 220 // Alias for bugprone-use-after-move. 221 "-hicpp-invalid-access-moved", 222 // Check uses dataflow analysis, which might hang/crash unexpectedly on 223 // incomplete code. 224 "-bugprone-unchecked-optional-access"); 225 226 size_t Size = BadChecks.size(); 227 for (const std::string &Str : ExtraBadChecks) { 228 if (Str.empty()) 229 continue; 230 Size += Separator.size(); 231 if (LLVM_LIKELY(Str.front() != '-')) 232 ++Size; 233 Size += Str.size(); 234 } 235 std::string DisableGlob; 236 DisableGlob.reserve(Size); 237 DisableGlob += BadChecks; 238 for (const std::string &Str : ExtraBadChecks) { 239 if (Str.empty()) 240 continue; 241 DisableGlob += Separator; 242 if (LLVM_LIKELY(Str.front() != '-')) 243 DisableGlob.push_back('-'); 244 DisableGlob += Str; 245 } 246 247 return [DisableList(std::move(DisableGlob))](tidy::ClangTidyOptions &Opts, 248 llvm::StringRef) { 249 if (Opts.Checks && !Opts.Checks->empty()) 250 Opts.Checks->append(DisableList); 251 }; 252 } 253 254 TidyProvider provideClangdConfig() { 255 return [](tidy::ClangTidyOptions &Opts, llvm::StringRef) { 256 const auto &CurTidyConfig = Config::current().Diagnostics.ClangTidy; 257 if (!CurTidyConfig.Checks.empty()) 258 mergeCheckList(Opts.Checks, CurTidyConfig.Checks); 259 260 for (const auto &CheckOption : CurTidyConfig.CheckOptions) 261 Opts.CheckOptions.insert_or_assign(CheckOption.getKey(), 262 tidy::ClangTidyOptions::ClangTidyValue( 263 CheckOption.getValue(), 10000U)); 264 }; 265 } 266 267 TidyProvider provideClangTidyFiles(ThreadsafeFS &TFS) { 268 return [Tree = std::make_unique<DotClangTidyTree>(TFS)]( 269 tidy::ClangTidyOptions &Opts, llvm::StringRef Filename) { 270 Tree->apply(Opts, Filename); 271 }; 272 } 273 274 TidyProvider combine(std::vector<TidyProvider> Providers) { 275 // FIXME: Once function_ref and unique_function operator= operators handle 276 // null values, we should filter out any Providers that are null. Right now we 277 // have to ensure we dont pass any providers that are null. 278 return [Providers(std::move(Providers))](tidy::ClangTidyOptions &Opts, 279 llvm::StringRef Filename) { 280 for (const auto &Provider : Providers) 281 Provider(Opts, Filename); 282 }; 283 } 284 285 tidy::ClangTidyOptions getTidyOptionsForFile(TidyProviderRef Provider, 286 llvm::StringRef Filename) { 287 // getDefaults instantiates all check factories, which are registered at link 288 // time. So cache the results once. 289 static const auto *DefaultOpts = [] { 290 auto *Opts = new tidy::ClangTidyOptions; 291 *Opts = tidy::ClangTidyOptions::getDefaults(); 292 Opts->Checks->clear(); 293 return Opts; 294 }(); 295 auto Opts = *DefaultOpts; 296 if (Provider) 297 Provider(Opts, Filename); 298 return Opts; 299 } 300 301 bool isRegisteredTidyCheck(llvm::StringRef Check) { 302 assert(!Check.empty()); 303 assert(!Check.contains('*') && !Check.contains(',') && 304 "isRegisteredCheck doesn't support globs"); 305 assert(Check.ltrim().front() != '-'); 306 307 static const llvm::StringSet<llvm::BumpPtrAllocator> AllChecks = [] { 308 llvm::StringSet<llvm::BumpPtrAllocator> Result; 309 tidy::ClangTidyCheckFactories Factories; 310 for (tidy::ClangTidyModuleRegistry::entry E : 311 tidy::ClangTidyModuleRegistry::entries()) 312 E.instantiate()->addCheckFactories(Factories); 313 for (const auto &Factory : Factories) 314 Result.insert(Factory.getKey()); 315 return Result; 316 }(); 317 318 return AllChecks.contains(Check); 319 } 320 321 std::optional<bool> isFastTidyCheck(llvm::StringRef Check) { 322 static auto &Fast = *new llvm::StringMap<bool>{ 323 #define FAST(CHECK, TIME) {#CHECK,true}, 324 #define SLOW(CHECK, TIME) {#CHECK,false}, 325 #include "TidyFastChecks.inc" 326 }; 327 if (auto It = Fast.find(Check); It != Fast.end()) 328 return It->second; 329 return std::nullopt; 330 } 331 332 } // namespace clangd 333 } // namespace clang 334