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 "Features.inc" 31 #include "TidyProvider.h" 32 #include "support/Logger.h" 33 #include "support/Trace.h" 34 #include "llvm/ADT/None.h" 35 #include "llvm/ADT/Optional.h" 36 #include "llvm/ADT/STLExtras.h" 37 #include "llvm/ADT/SmallString.h" 38 #include "llvm/ADT/StringRef.h" 39 #include "llvm/ADT/StringSwitch.h" 40 #include "llvm/Support/Error.h" 41 #include "llvm/Support/FileSystem.h" 42 #include "llvm/Support/Format.h" 43 #include "llvm/Support/FormatVariadic.h" 44 #include "llvm/Support/Path.h" 45 #include "llvm/Support/Regex.h" 46 #include "llvm/Support/SMLoc.h" 47 #include "llvm/Support/SourceMgr.h" 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 103 llvm::Optional<llvm::Regex> compileRegex(const Located<std::string> &Text) { 104 std::string Anchored = "^(" + *Text + ")$"; 105 llvm::Regex Result(Anchored); 106 std::string RegexError; 107 if (!Result.isValid(RegexError)) { 108 diag(Error, "Invalid regex " + Anchored + ": " + RegexError, Text.Range); 109 return llvm::None; 110 } 111 return Result; 112 } 113 114 llvm::Optional<std::string> makeAbsolute(Located<std::string> Path, 115 llvm::StringLiteral Description, 116 llvm::sys::path::Style Style) { 117 if (llvm::sys::path::is_absolute(*Path)) 118 return *Path; 119 if (FragmentDirectory.empty()) { 120 diag(Error, 121 llvm::formatv( 122 "{0} must be an absolute path, because this fragment is not " 123 "associated with any directory.", 124 Description) 125 .str(), 126 Path.Range); 127 return llvm::None; 128 } 129 llvm::SmallString<256> AbsPath = llvm::StringRef(*Path); 130 llvm::sys::fs::make_absolute(FragmentDirectory, AbsPath); 131 llvm::sys::path::native(AbsPath, Style); 132 return AbsPath.str().str(); 133 } 134 135 // Helper with similar API to StringSwitch, for parsing enum values. 136 template <typename T> class EnumSwitch { 137 FragmentCompiler &Outer; 138 llvm::StringRef EnumName; 139 const Located<std::string> &Input; 140 llvm::Optional<T> Result; 141 llvm::SmallVector<llvm::StringLiteral> ValidValues; 142 143 public: 144 EnumSwitch(llvm::StringRef EnumName, const Located<std::string> &In, 145 FragmentCompiler &Outer) 146 : Outer(Outer), EnumName(EnumName), Input(In) {} 147 148 EnumSwitch &map(llvm::StringLiteral Name, T Value) { 149 assert(!llvm::is_contained(ValidValues, Name) && "Duplicate value!"); 150 ValidValues.push_back(Name); 151 if (!Result && *Input == Name) 152 Result = Value; 153 return *this; 154 } 155 156 llvm::Optional<T> value() { 157 if (!Result) 158 Outer.diag( 159 Warning, 160 llvm::formatv("Invalid {0} value '{1}'. Valid values are {2}.", 161 EnumName, *Input, llvm::join(ValidValues, ", ")) 162 .str(), 163 Input.Range); 164 return Result; 165 }; 166 }; 167 168 // Attempt to parse a specified string into an enum. 169 // Yields llvm::None and produces a diagnostic on failure. 170 // 171 // Optional<T> Value = compileEnum<En>("Foo", Frag.Foo) 172 // .map("Foo", Enum::Foo) 173 // .map("Bar", Enum::Bar) 174 // .value(); 175 template <typename T> 176 EnumSwitch<T> compileEnum(llvm::StringRef EnumName, 177 const Located<std::string> &In) { 178 return EnumSwitch<T>(EnumName, In, *this); 179 } 180 181 void compile(Fragment &&F) { 182 if (!F.Source.Directory.empty()) { 183 FragmentDirectory = llvm::sys::path::convert_to_slash(F.Source.Directory); 184 if (FragmentDirectory.back() != '/') 185 FragmentDirectory += '/'; 186 } 187 compile(std::move(F.If)); 188 compile(std::move(F.CompileFlags)); 189 compile(std::move(F.Index)); 190 compile(std::move(F.ClangTidy)); 191 } 192 193 void compile(Fragment::IfBlock &&F) { 194 if (F.HasUnrecognizedCondition) 195 Out.Conditions.push_back([&](const Params &) { return false; }); 196 197 auto PathMatch = std::make_unique<std::vector<llvm::Regex>>(); 198 for (auto &Entry : F.PathMatch) { 199 if (auto RE = compileRegex(Entry)) 200 PathMatch->push_back(std::move(*RE)); 201 } 202 if (!PathMatch->empty()) { 203 Out.Conditions.push_back( 204 [PathMatch(std::move(PathMatch)), 205 FragmentDir(FragmentDirectory)](const Params &P) { 206 if (P.Path.empty()) 207 return false; 208 llvm::StringRef Path = configRelative(P.Path, FragmentDir); 209 // Ignore the file if it is not nested under Fragment. 210 if (Path.empty()) 211 return false; 212 return llvm::any_of(*PathMatch, [&](const llvm::Regex &RE) { 213 return RE.match(Path); 214 }); 215 }); 216 } 217 218 auto PathExclude = std::make_unique<std::vector<llvm::Regex>>(); 219 for (auto &Entry : F.PathExclude) { 220 if (auto RE = compileRegex(Entry)) 221 PathExclude->push_back(std::move(*RE)); 222 } 223 if (!PathExclude->empty()) { 224 Out.Conditions.push_back( 225 [PathExclude(std::move(PathExclude)), 226 FragmentDir(FragmentDirectory)](const Params &P) { 227 if (P.Path.empty()) 228 return false; 229 llvm::StringRef Path = configRelative(P.Path, FragmentDir); 230 // Ignore the file if it is not nested under Fragment. 231 if (Path.empty()) 232 return true; 233 return llvm::none_of(*PathExclude, [&](const llvm::Regex &RE) { 234 return RE.match(Path); 235 }); 236 }); 237 } 238 } 239 240 void compile(Fragment::CompileFlagsBlock &&F) { 241 if (!F.Remove.empty()) { 242 auto Remove = std::make_shared<ArgStripper>(); 243 for (auto &A : F.Remove) 244 Remove->strip(*A); 245 Out.Apply.push_back([Remove(std::shared_ptr<const ArgStripper>( 246 std::move(Remove)))](const Params &, Config &C) { 247 C.CompileFlags.Edits.push_back( 248 [Remove](std::vector<std::string> &Args) { 249 Remove->process(Args); 250 }); 251 }); 252 } 253 254 if (!F.Add.empty()) { 255 std::vector<std::string> Add; 256 for (auto &A : F.Add) 257 Add.push_back(std::move(*A)); 258 Out.Apply.push_back([Add(std::move(Add))](const Params &, Config &C) { 259 C.CompileFlags.Edits.push_back([Add](std::vector<std::string> &Args) { 260 Args.insert(Args.end(), Add.begin(), Add.end()); 261 }); 262 }); 263 } 264 } 265 266 void compile(Fragment::IndexBlock &&F) { 267 if (F.Background) { 268 if (auto Val = compileEnum<Config::BackgroundPolicy>("Background", 269 **F.Background) 270 .map("Build", Config::BackgroundPolicy::Build) 271 .map("Skip", Config::BackgroundPolicy::Skip) 272 .value()) 273 Out.Apply.push_back( 274 [Val](const Params &, Config &C) { C.Index.Background = *Val; }); 275 } 276 if (F.External) 277 compile(std::move(**F.External), F.External->Range); 278 } 279 280 void compile(Fragment::IndexBlock::ExternalBlock &&External, 281 llvm::SMRange BlockRange) { 282 #ifndef CLANGD_ENABLE_REMOTE 283 if (External.Server) { 284 diag(Error, "Clangd isn't compiled with remote index support, ignoring " 285 "Server." External.Server->Range); 286 External.Server.reset(); 287 } 288 #endif 289 // Make sure exactly one of the Sources is set. 290 unsigned SourceCount = 291 External.File.hasValue() + External.Server.hasValue(); 292 if (SourceCount != 1) { 293 diag(Error, "Exactly one of File or Server must be set.", BlockRange); 294 return; 295 } 296 Config::ExternalIndexSpec Spec; 297 if (External.Server) { 298 Spec.Kind = Config::ExternalIndexSpec::Server; 299 Spec.Location = std::move(**External.Server); 300 } else if (External.File) { 301 Spec.Kind = Config::ExternalIndexSpec::File; 302 auto AbsPath = makeAbsolute(std::move(*External.File), "File", 303 llvm::sys::path::Style::native); 304 if (!AbsPath) 305 return; 306 Spec.Location = std::move(*AbsPath); 307 } 308 // Make sure MountPoint is an absolute path with forward slashes. 309 if (!External.MountPoint) 310 External.MountPoint.emplace(FragmentDirectory); 311 if ((**External.MountPoint).empty()) { 312 diag(Error, "A mountpoint is required.", BlockRange); 313 return; 314 } 315 auto AbsPath = makeAbsolute(std::move(*External.MountPoint), "MountPoint", 316 llvm::sys::path::Style::posix); 317 if (!AbsPath) 318 return; 319 Spec.MountPoint = std::move(*AbsPath); 320 Out.Apply.push_back([Spec(std::move(Spec))](const Params &P, Config &C) { 321 if (!P.Path.startswith(Spec.MountPoint)) 322 return; 323 C.Index.External = Spec; 324 // Disable background indexing for the files under the mountpoint. 325 // Note that this will overwrite statements in any previous fragments 326 // (including the current one). 327 C.Index.Background = Config::BackgroundPolicy::Skip; 328 }); 329 } 330 331 void compile(Fragment::StyleBlock &&F) { 332 if (!F.FullyQualifiedNamespaces.empty()) { 333 std::vector<std::string> FullyQualifiedNamespaces; 334 for (auto &N : F.FullyQualifiedNamespaces) { 335 // Normalize the data by dropping both leading and trailing :: 336 StringRef Namespace(*N); 337 Namespace.consume_front("::"); 338 Namespace.consume_back("::"); 339 FullyQualifiedNamespaces.push_back(Namespace.str()); 340 } 341 Out.Apply.push_back([FullyQualifiedNamespaces( 342 std::move(FullyQualifiedNamespaces))]( 343 const Params &, Config &C) { 344 C.Style.FullyQualifiedNamespaces.insert( 345 C.Style.FullyQualifiedNamespaces.begin(), 346 FullyQualifiedNamespaces.begin(), FullyQualifiedNamespaces.end()); 347 }); 348 } 349 } 350 351 void appendTidyCheckSpec(std::string &CurSpec, 352 const Located<std::string> &Arg, bool IsPositive) { 353 StringRef Str = StringRef(*Arg).trim(); 354 // Don't support negating here, its handled if the item is in the Add or 355 // Remove list. 356 if (Str.startswith("-") || Str.contains(',')) { 357 diag(Error, "Invalid clang-tidy check name", Arg.Range); 358 return; 359 } 360 if (!Str.contains('*') && !isRegisteredTidyCheck(Str)) { 361 diag(Warning, 362 llvm::formatv("clang-tidy check '{0}' was not found", Str).str(), 363 Arg.Range); 364 return; 365 } 366 CurSpec += ','; 367 if (!IsPositive) 368 CurSpec += '-'; 369 CurSpec += Str; 370 } 371 372 void compile(Fragment::ClangTidyBlock &&F) { 373 std::string Checks; 374 for (auto &CheckGlob : F.Add) 375 appendTidyCheckSpec(Checks, CheckGlob, true); 376 377 for (auto &CheckGlob : F.Remove) 378 appendTidyCheckSpec(Checks, CheckGlob, false); 379 380 if (!Checks.empty()) 381 Out.Apply.push_back( 382 [Checks = std::move(Checks)](const Params &, Config &C) { 383 C.ClangTidy.Checks.append( 384 Checks, C.ClangTidy.Checks.empty() ? /*skip comma*/ 1 : 0, 385 std::string::npos); 386 }); 387 if (!F.CheckOptions.empty()) { 388 std::vector<std::pair<std::string, std::string>> CheckOptions; 389 for (auto &Opt : F.CheckOptions) 390 CheckOptions.emplace_back(std::move(*Opt.first), 391 std::move(*Opt.second)); 392 Out.Apply.push_back( 393 [CheckOptions = std::move(CheckOptions)](const Params &, Config &C) { 394 for (auto &StringPair : CheckOptions) 395 C.ClangTidy.CheckOptions.insert_or_assign(StringPair.first, 396 StringPair.second); 397 }); 398 } 399 } 400 401 constexpr static llvm::SourceMgr::DiagKind Error = llvm::SourceMgr::DK_Error; 402 constexpr static llvm::SourceMgr::DiagKind Warning = 403 llvm::SourceMgr::DK_Warning; 404 void diag(llvm::SourceMgr::DiagKind Kind, llvm::StringRef Message, 405 llvm::SMRange Range) { 406 if (Range.isValid() && SourceMgr != nullptr) 407 Diagnostic(SourceMgr->GetMessage(Range.Start, Kind, Message, Range)); 408 else 409 Diagnostic(llvm::SMDiagnostic("", Kind, Message)); 410 } 411 }; 412 413 } // namespace 414 415 CompiledFragment Fragment::compile(DiagnosticCallback D) && { 416 llvm::StringRef ConfigFile = "<unknown>"; 417 std::pair<unsigned, unsigned> LineCol = {0, 0}; 418 if (auto *SM = Source.Manager.get()) { 419 unsigned BufID = SM->getMainFileID(); 420 LineCol = SM->getLineAndColumn(Source.Location, BufID); 421 ConfigFile = SM->getBufferInfo(BufID).Buffer->getBufferIdentifier(); 422 } 423 trace::Span Tracer("ConfigCompile"); 424 SPAN_ATTACH(Tracer, "ConfigFile", ConfigFile); 425 auto Result = std::make_shared<CompiledFragmentImpl>(); 426 vlog("Config fragment: compiling {0}:{1} -> {2}", ConfigFile, LineCol.first, 427 Result.get()); 428 429 FragmentCompiler{*Result, D, Source.Manager.get()}.compile(std::move(*this)); 430 // Return as cheaply-copyable wrapper. 431 return [Result(std::move(Result))](const Params &P, Config &C) { 432 return (*Result)(P, C); 433 }; 434 } 435 436 } // namespace config 437 } // namespace clangd 438 } // namespace clang 439