1 //===--- ConfigCompile.cpp - Translating Fragments into Config ------------===// 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 // Fragments are applied to Configs in two steps: 10 // 11 // 1. (When the fragment is first loaded) 12 // FragmentCompiler::compile() traverses the Fragment and creates 13 // function objects that know how to apply the configuration. 14 // 2. (Every time a config is required) 15 // CompiledFragment() executes these functions to populate the Config. 16 // 17 // Work could be split between these steps in different ways. We try to 18 // do as much work as possible in the first step. For example, regexes are 19 // compiled in stage 1 and captured by the apply function. This is because: 20 // 21 // - it's more efficient, as the work done in stage 1 must only be done once 22 // - problems can be reported in stage 1, in stage 2 we must silently recover 23 // 24 //===----------------------------------------------------------------------===// 25 26 #include "CompileCommands.h" 27 #include "Config.h" 28 #include "ConfigFragment.h" 29 #include "ConfigProvider.h" 30 #include "Diagnostics.h" 31 #include "Feature.h" 32 #include "TidyProvider.h" 33 #include "support/Logger.h" 34 #include "support/Path.h" 35 #include "support/Trace.h" 36 #include "llvm/ADT/None.h" 37 #include "llvm/ADT/Optional.h" 38 #include "llvm/ADT/STLExtras.h" 39 #include "llvm/ADT/SmallString.h" 40 #include "llvm/ADT/StringRef.h" 41 #include "llvm/Support/FileSystem.h" 42 #include "llvm/Support/FormatVariadic.h" 43 #include "llvm/Support/Path.h" 44 #include "llvm/Support/Regex.h" 45 #include "llvm/Support/SMLoc.h" 46 #include "llvm/Support/SourceMgr.h" 47 #include <algorithm> 48 #include <string> 49 50 namespace clang { 51 namespace clangd { 52 namespace config { 53 namespace { 54 55 // Returns an empty stringref if Path is not under FragmentDir. Returns Path 56 // as-is when FragmentDir is empty. 57 llvm::StringRef configRelative(llvm::StringRef Path, 58 llvm::StringRef FragmentDir) { 59 if (FragmentDir.empty()) 60 return Path; 61 if (!Path.consume_front(FragmentDir)) 62 return llvm::StringRef(); 63 return Path.empty() ? "." : Path; 64 } 65 66 struct CompiledFragmentImpl { 67 // The independent conditions to check before using settings from this config. 68 // The following fragment has *two* conditions: 69 // If: { Platform: [mac, linux], PathMatch: foo/.* } 70 // All of them must be satisfied: the platform and path conditions are ANDed. 71 // The OR logic for the platform condition is implemented inside the function. 72 std::vector<llvm::unique_function<bool(const Params &) const>> Conditions; 73 // Mutations that this fragment will apply to the configuration. 74 // These are invoked only if the conditions are satisfied. 75 std::vector<llvm::unique_function<void(const Params &, Config &) const>> 76 Apply; 77 78 bool operator()(const Params &P, Config &C) const { 79 for (const auto &C : Conditions) { 80 if (!C(P)) { 81 dlog("Config fragment {0}: condition not met", this); 82 return false; 83 } 84 } 85 dlog("Config fragment {0}: applying {1} rules", this, Apply.size()); 86 for (const auto &A : Apply) 87 A(P, C); 88 return true; 89 } 90 }; 91 92 // Wrapper around condition compile() functions to reduce arg-passing. 93 struct FragmentCompiler { 94 FragmentCompiler(CompiledFragmentImpl &Out, DiagnosticCallback D, 95 llvm::SourceMgr *SM) 96 : Out(Out), Diagnostic(D), SourceMgr(SM) {} 97 CompiledFragmentImpl &Out; 98 DiagnosticCallback Diagnostic; 99 llvm::SourceMgr *SourceMgr; 100 // Normalized Fragment::SourceInfo::Directory. 101 std::string FragmentDirectory; 102 bool Trusted = false; 103 104 llvm::Optional<llvm::Regex> 105 compileRegex(const Located<std::string> &Text, 106 llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags) { 107 std::string Anchored = "^(" + *Text + ")$"; 108 llvm::Regex Result(Anchored, Flags); 109 std::string RegexError; 110 if (!Result.isValid(RegexError)) { 111 diag(Error, "Invalid regex " + Anchored + ": " + RegexError, Text.Range); 112 return llvm::None; 113 } 114 return Result; 115 } 116 117 llvm::Optional<std::string> makeAbsolute(Located<std::string> Path, 118 llvm::StringLiteral Description, 119 llvm::sys::path::Style Style) { 120 if (llvm::sys::path::is_absolute(*Path)) 121 return *Path; 122 if (FragmentDirectory.empty()) { 123 diag(Error, 124 llvm::formatv( 125 "{0} must be an absolute path, because this fragment is not " 126 "associated with any directory.", 127 Description) 128 .str(), 129 Path.Range); 130 return llvm::None; 131 } 132 llvm::SmallString<256> AbsPath = llvm::StringRef(*Path); 133 llvm::sys::fs::make_absolute(FragmentDirectory, AbsPath); 134 llvm::sys::path::native(AbsPath, Style); 135 return AbsPath.str().str(); 136 } 137 138 // Helper with similar API to StringSwitch, for parsing enum values. 139 template <typename T> class EnumSwitch { 140 FragmentCompiler &Outer; 141 llvm::StringRef EnumName; 142 const Located<std::string> &Input; 143 llvm::Optional<T> Result; 144 llvm::SmallVector<llvm::StringLiteral> ValidValues; 145 146 public: 147 EnumSwitch(llvm::StringRef EnumName, const Located<std::string> &In, 148 FragmentCompiler &Outer) 149 : Outer(Outer), EnumName(EnumName), Input(In) {} 150 151 EnumSwitch &map(llvm::StringLiteral Name, T Value) { 152 assert(!llvm::is_contained(ValidValues, Name) && "Duplicate value!"); 153 ValidValues.push_back(Name); 154 if (!Result && *Input == Name) 155 Result = Value; 156 return *this; 157 } 158 159 llvm::Optional<T> value() { 160 if (!Result) 161 Outer.diag( 162 Warning, 163 llvm::formatv("Invalid {0} value '{1}'. Valid values are {2}.", 164 EnumName, *Input, llvm::join(ValidValues, ", ")) 165 .str(), 166 Input.Range); 167 return Result; 168 }; 169 }; 170 171 // Attempt to parse a specified string into an enum. 172 // Yields llvm::None and produces a diagnostic on failure. 173 // 174 // Optional<T> Value = compileEnum<En>("Foo", Frag.Foo) 175 // .map("Foo", Enum::Foo) 176 // .map("Bar", Enum::Bar) 177 // .value(); 178 template <typename T> 179 EnumSwitch<T> compileEnum(llvm::StringRef EnumName, 180 const Located<std::string> &In) { 181 return EnumSwitch<T>(EnumName, In, *this); 182 } 183 184 void compile(Fragment &&F) { 185 Trusted = F.Source.Trusted; 186 if (!F.Source.Directory.empty()) { 187 FragmentDirectory = llvm::sys::path::convert_to_slash(F.Source.Directory); 188 if (FragmentDirectory.back() != '/') 189 FragmentDirectory += '/'; 190 } 191 compile(std::move(F.If)); 192 compile(std::move(F.CompileFlags)); 193 compile(std::move(F.Index)); 194 compile(std::move(F.Diagnostics)); 195 compile(std::move(F.Completion)); 196 compile(std::move(F.Hover)); 197 compile(std::move(F.InlayHints)); 198 } 199 200 void compile(Fragment::IfBlock &&F) { 201 if (F.HasUnrecognizedCondition) 202 Out.Conditions.push_back([&](const Params &) { return false; }); 203 204 #ifdef CLANGD_PATH_CASE_INSENSITIVE 205 llvm::Regex::RegexFlags Flags = llvm::Regex::IgnoreCase; 206 #else 207 llvm::Regex::RegexFlags Flags = llvm::Regex::NoFlags; 208 #endif 209 210 auto PathMatch = std::make_unique<std::vector<llvm::Regex>>(); 211 for (auto &Entry : F.PathMatch) { 212 if (auto RE = compileRegex(Entry, Flags)) 213 PathMatch->push_back(std::move(*RE)); 214 } 215 if (!PathMatch->empty()) { 216 Out.Conditions.push_back( 217 [PathMatch(std::move(PathMatch)), 218 FragmentDir(FragmentDirectory)](const Params &P) { 219 if (P.Path.empty()) 220 return false; 221 llvm::StringRef Path = configRelative(P.Path, FragmentDir); 222 // Ignore the file if it is not nested under Fragment. 223 if (Path.empty()) 224 return false; 225 return llvm::any_of(*PathMatch, [&](const llvm::Regex &RE) { 226 return RE.match(Path); 227 }); 228 }); 229 } 230 231 auto PathExclude = std::make_unique<std::vector<llvm::Regex>>(); 232 for (auto &Entry : F.PathExclude) { 233 if (auto RE = compileRegex(Entry, Flags)) 234 PathExclude->push_back(std::move(*RE)); 235 } 236 if (!PathExclude->empty()) { 237 Out.Conditions.push_back( 238 [PathExclude(std::move(PathExclude)), 239 FragmentDir(FragmentDirectory)](const Params &P) { 240 if (P.Path.empty()) 241 return false; 242 llvm::StringRef Path = configRelative(P.Path, FragmentDir); 243 // Ignore the file if it is not nested under Fragment. 244 if (Path.empty()) 245 return true; 246 return llvm::none_of(*PathExclude, [&](const llvm::Regex &RE) { 247 return RE.match(Path); 248 }); 249 }); 250 } 251 } 252 253 void compile(Fragment::CompileFlagsBlock &&F) { 254 if (F.Compiler) 255 Out.Apply.push_back( 256 [Compiler(std::move(**F.Compiler))](const Params &, Config &C) { 257 C.CompileFlags.Edits.push_back( 258 [Compiler](std::vector<std::string> &Args) { 259 if (!Args.empty()) 260 Args.front() = Compiler; 261 }); 262 }); 263 264 if (!F.Remove.empty()) { 265 auto Remove = std::make_shared<ArgStripper>(); 266 for (auto &A : F.Remove) 267 Remove->strip(*A); 268 Out.Apply.push_back([Remove(std::shared_ptr<const ArgStripper>( 269 std::move(Remove)))](const Params &, Config &C) { 270 C.CompileFlags.Edits.push_back( 271 [Remove](std::vector<std::string> &Args) { 272 Remove->process(Args); 273 }); 274 }); 275 } 276 277 if (!F.Add.empty()) { 278 std::vector<std::string> Add; 279 for (auto &A : F.Add) 280 Add.push_back(std::move(*A)); 281 Out.Apply.push_back([Add(std::move(Add))](const Params &, Config &C) { 282 C.CompileFlags.Edits.push_back([Add](std::vector<std::string> &Args) { 283 // The point to insert at. Just append when `--` isn't present. 284 auto It = llvm::find(Args, "--"); 285 Args.insert(It, Add.begin(), Add.end()); 286 }); 287 }); 288 } 289 290 if (F.CompilationDatabase) { 291 llvm::Optional<Config::CDBSearchSpec> Spec; 292 if (**F.CompilationDatabase == "Ancestors") { 293 Spec.emplace(); 294 Spec->Policy = Config::CDBSearchSpec::Ancestors; 295 } else if (**F.CompilationDatabase == "None") { 296 Spec.emplace(); 297 Spec->Policy = Config::CDBSearchSpec::NoCDBSearch; 298 } else { 299 if (auto Path = 300 makeAbsolute(*F.CompilationDatabase, "CompilationDatabase", 301 llvm::sys::path::Style::native)) { 302 // Drop trailing slash to put the path in canonical form. 303 // Should makeAbsolute do this? 304 llvm::StringRef Rel = llvm::sys::path::relative_path(*Path); 305 if (!Rel.empty() && llvm::sys::path::is_separator(Rel.back())) 306 Path->pop_back(); 307 308 Spec.emplace(); 309 Spec->Policy = Config::CDBSearchSpec::FixedDir; 310 Spec->FixedCDBPath = std::move(Path); 311 } 312 } 313 if (Spec) 314 Out.Apply.push_back( 315 [Spec(std::move(*Spec))](const Params &, Config &C) { 316 C.CompileFlags.CDBSearch = Spec; 317 }); 318 } 319 } 320 321 void compile(Fragment::IndexBlock &&F) { 322 if (F.Background) { 323 if (auto Val = compileEnum<Config::BackgroundPolicy>("Background", 324 **F.Background) 325 .map("Build", Config::BackgroundPolicy::Build) 326 .map("Skip", Config::BackgroundPolicy::Skip) 327 .value()) 328 Out.Apply.push_back( 329 [Val](const Params &, Config &C) { C.Index.Background = *Val; }); 330 } 331 if (F.External) 332 compile(std::move(**F.External), F.External->Range); 333 } 334 335 void compile(Fragment::IndexBlock::ExternalBlock &&External, 336 llvm::SMRange BlockRange) { 337 if (External.Server && !Trusted) { 338 diag(Error, 339 "Remote index may not be specified by untrusted configuration. " 340 "Copy this into user config to use it.", 341 External.Server->Range); 342 return; 343 } 344 #ifndef CLANGD_ENABLE_REMOTE 345 if (External.Server) { 346 elog("Clangd isn't compiled with remote index support, ignoring Server: " 347 "{0}", 348 *External.Server); 349 External.Server.reset(); 350 } 351 #endif 352 // Make sure exactly one of the Sources is set. 353 unsigned SourceCount = External.File.hasValue() + 354 External.Server.hasValue() + *External.IsNone; 355 if (SourceCount != 1) { 356 diag(Error, "Exactly one of File, Server or None must be set.", 357 BlockRange); 358 return; 359 } 360 Config::ExternalIndexSpec Spec; 361 if (External.Server) { 362 Spec.Kind = Config::ExternalIndexSpec::Server; 363 Spec.Location = std::move(**External.Server); 364 } else if (External.File) { 365 Spec.Kind = Config::ExternalIndexSpec::File; 366 auto AbsPath = makeAbsolute(std::move(*External.File), "File", 367 llvm::sys::path::Style::native); 368 if (!AbsPath) 369 return; 370 Spec.Location = std::move(*AbsPath); 371 } else { 372 assert(*External.IsNone); 373 Spec.Kind = Config::ExternalIndexSpec::None; 374 } 375 if (Spec.Kind != Config::ExternalIndexSpec::None) { 376 // Make sure MountPoint is an absolute path with forward slashes. 377 if (!External.MountPoint) 378 External.MountPoint.emplace(FragmentDirectory); 379 if ((**External.MountPoint).empty()) { 380 diag(Error, "A mountpoint is required.", BlockRange); 381 return; 382 } 383 auto AbsPath = makeAbsolute(std::move(*External.MountPoint), "MountPoint", 384 llvm::sys::path::Style::posix); 385 if (!AbsPath) 386 return; 387 Spec.MountPoint = std::move(*AbsPath); 388 } 389 Out.Apply.push_back([Spec(std::move(Spec))](const Params &P, Config &C) { 390 if (Spec.Kind == Config::ExternalIndexSpec::None) { 391 C.Index.External = Spec; 392 return; 393 } 394 if (P.Path.empty() || !pathStartsWith(Spec.MountPoint, P.Path, 395 llvm::sys::path::Style::posix)) 396 return; 397 C.Index.External = Spec; 398 // Disable background indexing for the files under the mountpoint. 399 // Note that this will overwrite statements in any previous fragments 400 // (including the current one). 401 C.Index.Background = Config::BackgroundPolicy::Skip; 402 }); 403 } 404 405 void compile(Fragment::DiagnosticsBlock &&F) { 406 std::vector<std::string> Normalized; 407 for (const auto &Suppressed : F.Suppress) { 408 if (*Suppressed == "*") { 409 Out.Apply.push_back([&](const Params &, Config &C) { 410 C.Diagnostics.SuppressAll = true; 411 C.Diagnostics.Suppress.clear(); 412 }); 413 return; 414 } 415 Normalized.push_back(normalizeSuppressedCode(*Suppressed).str()); 416 } 417 if (!Normalized.empty()) 418 Out.Apply.push_back( 419 [Normalized(std::move(Normalized))](const Params &, Config &C) { 420 if (C.Diagnostics.SuppressAll) 421 return; 422 for (llvm::StringRef N : Normalized) 423 C.Diagnostics.Suppress.insert(N); 424 }); 425 426 if (F.UnusedIncludes) 427 if (auto Val = compileEnum<Config::UnusedIncludesPolicy>( 428 "UnusedIncludes", **F.UnusedIncludes) 429 .map("Strict", Config::UnusedIncludesPolicy::Strict) 430 .map("None", Config::UnusedIncludesPolicy::None) 431 .value()) 432 Out.Apply.push_back([Val](const Params &, Config &C) { 433 C.Diagnostics.UnusedIncludes = *Val; 434 }); 435 436 compile(std::move(F.ClangTidy)); 437 } 438 439 void compile(Fragment::StyleBlock &&F) { 440 if (!F.FullyQualifiedNamespaces.empty()) { 441 std::vector<std::string> FullyQualifiedNamespaces; 442 for (auto &N : F.FullyQualifiedNamespaces) { 443 // Normalize the data by dropping both leading and trailing :: 444 StringRef Namespace(*N); 445 Namespace.consume_front("::"); 446 Namespace.consume_back("::"); 447 FullyQualifiedNamespaces.push_back(Namespace.str()); 448 } 449 Out.Apply.push_back([FullyQualifiedNamespaces( 450 std::move(FullyQualifiedNamespaces))]( 451 const Params &, Config &C) { 452 C.Style.FullyQualifiedNamespaces.insert( 453 C.Style.FullyQualifiedNamespaces.begin(), 454 FullyQualifiedNamespaces.begin(), FullyQualifiedNamespaces.end()); 455 }); 456 } 457 } 458 459 void appendTidyCheckSpec(std::string &CurSpec, 460 const Located<std::string> &Arg, bool IsPositive) { 461 StringRef Str = StringRef(*Arg).trim(); 462 // Don't support negating here, its handled if the item is in the Add or 463 // Remove list. 464 if (Str.startswith("-") || Str.contains(',')) { 465 diag(Error, "Invalid clang-tidy check name", Arg.Range); 466 return; 467 } 468 if (!Str.contains('*') && !isRegisteredTidyCheck(Str)) { 469 diag(Warning, 470 llvm::formatv("clang-tidy check '{0}' was not found", Str).str(), 471 Arg.Range); 472 return; 473 } 474 CurSpec += ','; 475 if (!IsPositive) 476 CurSpec += '-'; 477 CurSpec += Str; 478 } 479 480 void compile(Fragment::DiagnosticsBlock::ClangTidyBlock &&F) { 481 std::string Checks; 482 for (auto &CheckGlob : F.Add) 483 appendTidyCheckSpec(Checks, CheckGlob, true); 484 485 for (auto &CheckGlob : F.Remove) 486 appendTidyCheckSpec(Checks, CheckGlob, false); 487 488 if (!Checks.empty()) 489 Out.Apply.push_back( 490 [Checks = std::move(Checks)](const Params &, Config &C) { 491 C.Diagnostics.ClangTidy.Checks.append( 492 Checks, 493 C.Diagnostics.ClangTidy.Checks.empty() ? /*skip comma*/ 1 : 0, 494 std::string::npos); 495 }); 496 if (!F.CheckOptions.empty()) { 497 std::vector<std::pair<std::string, std::string>> CheckOptions; 498 for (auto &Opt : F.CheckOptions) 499 CheckOptions.emplace_back(std::move(*Opt.first), 500 std::move(*Opt.second)); 501 Out.Apply.push_back( 502 [CheckOptions = std::move(CheckOptions)](const Params &, Config &C) { 503 for (auto &StringPair : CheckOptions) 504 C.Diagnostics.ClangTidy.CheckOptions.insert_or_assign( 505 StringPair.first, StringPair.second); 506 }); 507 } 508 } 509 510 void compile(Fragment::CompletionBlock &&F) { 511 if (F.AllScopes) { 512 Out.Apply.push_back( 513 [AllScopes(**F.AllScopes)](const Params &, Config &C) { 514 C.Completion.AllScopes = AllScopes; 515 }); 516 } 517 } 518 519 void compile(Fragment::HoverBlock &&F) { 520 if (F.ShowAKA) { 521 Out.Apply.push_back([ShowAKA(**F.ShowAKA)](const Params &, Config &C) { 522 C.Hover.ShowAKA = ShowAKA; 523 }); 524 } 525 } 526 527 void compile(Fragment::InlayHintsBlock &&F) { 528 if (F.Enabled) 529 Out.Apply.push_back([Value(**F.Enabled)](const Params &, Config &C) { 530 C.InlayHints.Enabled = Value; 531 }); 532 if (F.ParameterNames) 533 Out.Apply.push_back( 534 [Value(**F.ParameterNames)](const Params &, Config &C) { 535 C.InlayHints.Parameters = Value; 536 }); 537 if (F.DeducedTypes) 538 Out.Apply.push_back([Value(**F.DeducedTypes)](const Params &, Config &C) { 539 C.InlayHints.DeducedTypes = Value; 540 }); 541 if (F.Designators) 542 Out.Apply.push_back([Value(**F.Designators)](const Params &, Config &C) { 543 C.InlayHints.Designators = Value; 544 }); 545 } 546 547 constexpr static llvm::SourceMgr::DiagKind Error = llvm::SourceMgr::DK_Error; 548 constexpr static llvm::SourceMgr::DiagKind Warning = 549 llvm::SourceMgr::DK_Warning; 550 void diag(llvm::SourceMgr::DiagKind Kind, llvm::StringRef Message, 551 llvm::SMRange Range) { 552 if (Range.isValid() && SourceMgr != nullptr) 553 Diagnostic(SourceMgr->GetMessage(Range.Start, Kind, Message, Range)); 554 else 555 Diagnostic(llvm::SMDiagnostic("", Kind, Message)); 556 } 557 }; 558 559 } // namespace 560 561 CompiledFragment Fragment::compile(DiagnosticCallback D) && { 562 llvm::StringRef ConfigFile = "<unknown>"; 563 std::pair<unsigned, unsigned> LineCol = {0, 0}; 564 if (auto *SM = Source.Manager.get()) { 565 unsigned BufID = SM->getMainFileID(); 566 LineCol = SM->getLineAndColumn(Source.Location, BufID); 567 ConfigFile = SM->getBufferInfo(BufID).Buffer->getBufferIdentifier(); 568 } 569 trace::Span Tracer("ConfigCompile"); 570 SPAN_ATTACH(Tracer, "ConfigFile", ConfigFile); 571 auto Result = std::make_shared<CompiledFragmentImpl>(); 572 vlog("Config fragment: compiling {0}:{1} -> {2} (trusted={3})", ConfigFile, 573 LineCol.first, Result.get(), Source.Trusted); 574 575 FragmentCompiler{*Result, D, Source.Manager.get()}.compile(std::move(*this)); 576 // Return as cheaply-copyable wrapper. 577 return [Result(std::move(Result))](const Params &P, Config &C) { 578 return (*Result)(P, C); 579 }; 580 } 581 582 } // namespace config 583 } // namespace clangd 584 } // namespace clang 585