1 //===--- ConfigYAML.cpp - Loading configuration fragments from YAML files -===// 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 #include "ConfigFragment.h" 9 #include "llvm/ADT/Optional.h" 10 #include "llvm/ADT/SmallSet.h" 11 #include "llvm/ADT/StringRef.h" 12 #include "llvm/Support/MemoryBuffer.h" 13 #include "llvm/Support/SourceMgr.h" 14 #include "llvm/Support/YAMLParser.h" 15 #include <string> 16 #include <system_error> 17 18 namespace clang { 19 namespace clangd { 20 namespace config { 21 namespace { 22 using llvm::yaml::BlockScalarNode; 23 using llvm::yaml::MappingNode; 24 using llvm::yaml::Node; 25 using llvm::yaml::ScalarNode; 26 using llvm::yaml::SequenceNode; 27 28 llvm::Optional<llvm::StringRef> 29 bestGuess(llvm::StringRef Search, 30 llvm::ArrayRef<llvm::StringRef> AllowedValues) { 31 unsigned MaxEdit = (Search.size() + 1) / 3; 32 if (!MaxEdit) 33 return llvm::None; 34 llvm::Optional<llvm::StringRef> Result; 35 for (const auto &AllowedValue : AllowedValues) { 36 unsigned EditDistance = Search.edit_distance(AllowedValue, true, MaxEdit); 37 // We can't do better than an edit distance of 1, so just return this and 38 // save computing other values. 39 if (EditDistance == 1U) 40 return AllowedValue; 41 if (EditDistance == MaxEdit && !Result) { 42 Result = AllowedValue; 43 } else if (EditDistance < MaxEdit) { 44 Result = AllowedValue; 45 MaxEdit = EditDistance; 46 } 47 } 48 return Result; 49 } 50 51 class Parser { 52 llvm::SourceMgr &SM; 53 bool HadError = false; 54 55 public: 56 Parser(llvm::SourceMgr &SM) : SM(SM) {} 57 58 // Tries to parse N into F, returning false if it failed and we couldn't 59 // meaningfully recover (YAML syntax error, or hard semantic error). 60 bool parse(Fragment &F, Node &N) { 61 DictParser Dict("Config", this); 62 Dict.handle("If", [&](Node &N) { parse(F.If, N); }); 63 Dict.handle("CompileFlags", [&](Node &N) { parse(F.CompileFlags, N); }); 64 Dict.handle("Index", [&](Node &N) { parse(F.Index, N); }); 65 Dict.handle("Style", [&](Node &N) { parse(F.Style, N); }); 66 Dict.handle("Diagnostics", [&](Node &N) { parse(F.Diagnostics, N); }); 67 Dict.handle("Completion", [&](Node &N) { parse(F.Completion, N); }); 68 Dict.handle("Hover", [&](Node &N) { parse(F.Hover, N); }); 69 Dict.parse(N); 70 return !(N.failed() || HadError); 71 } 72 73 private: 74 void parse(Fragment::IfBlock &F, Node &N) { 75 DictParser Dict("If", this); 76 Dict.unrecognized([&](Located<std::string>, Node &) { 77 F.HasUnrecognizedCondition = true; 78 return true; // Emit a warning for the unrecognized key. 79 }); 80 Dict.handle("PathMatch", [&](Node &N) { 81 if (auto Values = scalarValues(N)) 82 F.PathMatch = std::move(*Values); 83 }); 84 Dict.handle("PathExclude", [&](Node &N) { 85 if (auto Values = scalarValues(N)) 86 F.PathExclude = std::move(*Values); 87 }); 88 Dict.parse(N); 89 } 90 91 void parse(Fragment::CompileFlagsBlock &F, Node &N) { 92 DictParser Dict("CompileFlags", this); 93 Dict.handle("Add", [&](Node &N) { 94 if (auto Values = scalarValues(N)) 95 F.Add = std::move(*Values); 96 }); 97 Dict.handle("Remove", [&](Node &N) { 98 if (auto Values = scalarValues(N)) 99 F.Remove = std::move(*Values); 100 }); 101 Dict.handle("CompilationDatabase", [&](Node &N) { 102 F.CompilationDatabase = scalarValue(N, "CompilationDatabase"); 103 }); 104 Dict.parse(N); 105 } 106 107 void parse(Fragment::StyleBlock &F, Node &N) { 108 DictParser Dict("Style", this); 109 Dict.handle("FullyQualifiedNamespaces", [&](Node &N) { 110 if (auto Values = scalarValues(N)) 111 F.FullyQualifiedNamespaces = std::move(*Values); 112 }); 113 Dict.parse(N); 114 } 115 116 void parse(Fragment::DiagnosticsBlock &F, Node &N) { 117 DictParser Dict("Diagnostics", this); 118 Dict.handle("Suppress", [&](Node &N) { 119 if (auto Values = scalarValues(N)) 120 F.Suppress = std::move(*Values); 121 }); 122 Dict.handle("UnusedIncludes", [&](Node &N) { 123 F.UnusedIncludes = scalarValue(N, "UnusedIncludes"); 124 }); 125 Dict.handle("ClangTidy", [&](Node &N) { parse(F.ClangTidy, N); }); 126 Dict.parse(N); 127 } 128 129 void parse(Fragment::DiagnosticsBlock::ClangTidyBlock &F, Node &N) { 130 DictParser Dict("ClangTidy", this); 131 Dict.handle("Add", [&](Node &N) { 132 if (auto Values = scalarValues(N)) 133 F.Add = std::move(*Values); 134 }); 135 Dict.handle("Remove", [&](Node &N) { 136 if (auto Values = scalarValues(N)) 137 F.Remove = std::move(*Values); 138 }); 139 Dict.handle("CheckOptions", [&](Node &N) { 140 DictParser CheckOptDict("CheckOptions", this); 141 CheckOptDict.unrecognized([&](Located<std::string> &&Key, Node &Val) { 142 if (auto Value = scalarValue(Val, *Key)) 143 F.CheckOptions.emplace_back(std::move(Key), std::move(*Value)); 144 return false; // Don't emit a warning 145 }); 146 CheckOptDict.parse(N); 147 }); 148 Dict.parse(N); 149 } 150 151 void parse(Fragment::IndexBlock &F, Node &N) { 152 DictParser Dict("Index", this); 153 Dict.handle("Background", 154 [&](Node &N) { F.Background = scalarValue(N, "Background"); }); 155 Dict.handle("External", [&](Node &N) { 156 Fragment::IndexBlock::ExternalBlock External; 157 // External block can either be a mapping or a scalar value. Dispatch 158 // accordingly. 159 if (N.getType() == Node::NK_Mapping) { 160 parse(External, N); 161 } else if (N.getType() == Node::NK_Scalar || 162 N.getType() == Node::NK_BlockScalar) { 163 parse(External, scalarValue(N, "External").getValue()); 164 } else { 165 error("External must be either a scalar or a mapping.", N); 166 return; 167 } 168 F.External.emplace(std::move(External)); 169 F.External->Range = N.getSourceRange(); 170 }); 171 Dict.parse(N); 172 } 173 174 void parse(Fragment::IndexBlock::ExternalBlock &F, 175 Located<std::string> ExternalVal) { 176 if (!llvm::StringRef(*ExternalVal).equals_insensitive("none")) { 177 error("Only scalar value supported for External is 'None'", 178 ExternalVal.Range); 179 return; 180 } 181 F.IsNone = true; 182 F.IsNone.Range = ExternalVal.Range; 183 } 184 185 void parse(Fragment::IndexBlock::ExternalBlock &F, Node &N) { 186 DictParser Dict("External", this); 187 Dict.handle("File", [&](Node &N) { F.File = scalarValue(N, "File"); }); 188 Dict.handle("Server", 189 [&](Node &N) { F.Server = scalarValue(N, "Server"); }); 190 Dict.handle("MountPoint", 191 [&](Node &N) { F.MountPoint = scalarValue(N, "MountPoint"); }); 192 Dict.parse(N); 193 } 194 195 void parse(Fragment::CompletionBlock &F, Node &N) { 196 DictParser Dict("Completion", this); 197 Dict.handle("AllScopes", [&](Node &N) { 198 if (auto Value = scalarValue(N, "AllScopes")) { 199 if (auto AllScopes = llvm::yaml::parseBool(**Value)) 200 F.AllScopes = *AllScopes; 201 else 202 warning("AllScopes should be a boolean", N); 203 } 204 }); 205 Dict.parse(N); 206 } 207 208 void parse(Fragment::HoverBlock &F, Node &N) { 209 DictParser Dict("Hover", this); 210 Dict.handle("ShowAKA", [&](Node &N) { 211 if (auto Value = scalarValue(N, "ShowAKA")) { 212 if (auto ShowAKA = llvm::yaml::parseBool(**Value)) 213 F.ShowAKA = *ShowAKA; 214 else 215 warning("ShowAKA should be a boolean", N); 216 } 217 }); 218 Dict.parse(N); 219 } 220 221 // Helper for parsing mapping nodes (dictionaries). 222 // We don't use YamlIO as we want to control over unknown keys. 223 class DictParser { 224 llvm::StringRef Description; 225 std::vector<std::pair<llvm::StringRef, std::function<void(Node &)>>> Keys; 226 std::function<bool(Located<std::string>, Node &)> UnknownHandler; 227 Parser *Outer; 228 229 public: 230 DictParser(llvm::StringRef Description, Parser *Outer) 231 : Description(Description), Outer(Outer) {} 232 233 // Parse is called when Key is encountered, and passed the associated value. 234 // It should emit diagnostics if the value is invalid (e.g. wrong type). 235 // If Key is seen twice, Parse runs only once and an error is reported. 236 void handle(llvm::StringLiteral Key, std::function<void(Node &)> Parse) { 237 for (const auto &Entry : Keys) { 238 (void) Entry; 239 assert(Entry.first != Key && "duplicate key handler"); 240 } 241 Keys.emplace_back(Key, std::move(Parse)); 242 } 243 244 // Handler is called when a Key is not matched by any handle(). 245 // If this is unset or the Handler returns true, a warning is emitted for 246 // the unknown key. 247 void 248 unrecognized(std::function<bool(Located<std::string>, Node &)> Handler) { 249 UnknownHandler = std::move(Handler); 250 } 251 252 // Process a mapping node and call handlers for each key/value pair. 253 void parse(Node &N) const { 254 if (N.getType() != Node::NK_Mapping) { 255 Outer->error(Description + " should be a dictionary", N); 256 return; 257 } 258 llvm::SmallSet<std::string, 8> Seen; 259 llvm::SmallVector<Located<std::string>, 0> UnknownKeys; 260 // We *must* consume all items, even on error, or the parser will assert. 261 for (auto &KV : llvm::cast<MappingNode>(N)) { 262 auto *K = KV.getKey(); 263 if (!K) // YAMLParser emitted an error. 264 continue; 265 auto Key = Outer->scalarValue(*K, "Dictionary key"); 266 if (!Key) 267 continue; 268 if (!Seen.insert(**Key).second) { 269 Outer->warning("Duplicate key " + **Key + " is ignored", *K); 270 if (auto *Value = KV.getValue()) 271 Value->skip(); 272 continue; 273 } 274 auto *Value = KV.getValue(); 275 if (!Value) // YAMLParser emitted an error. 276 continue; 277 bool Matched = false; 278 for (const auto &Handler : Keys) { 279 if (Handler.first == **Key) { 280 Matched = true; 281 Handler.second(*Value); 282 break; 283 } 284 } 285 if (!Matched) { 286 bool Warn = !UnknownHandler; 287 if (UnknownHandler) 288 Warn = UnknownHandler( 289 Located<std::string>(**Key, K->getSourceRange()), *Value); 290 if (Warn) 291 UnknownKeys.push_back(std::move(*Key)); 292 } 293 } 294 if (!UnknownKeys.empty()) 295 warnUnknownKeys(UnknownKeys, Seen); 296 } 297 298 private: 299 void warnUnknownKeys(llvm::ArrayRef<Located<std::string>> UnknownKeys, 300 const llvm::SmallSet<std::string, 8> &SeenKeys) const { 301 llvm::SmallVector<llvm::StringRef> UnseenKeys; 302 for (const auto &KeyAndHandler : Keys) 303 if (!SeenKeys.count(KeyAndHandler.first.str())) 304 UnseenKeys.push_back(KeyAndHandler.first); 305 306 for (const Located<std::string> &UnknownKey : UnknownKeys) 307 if (auto BestGuess = bestGuess(*UnknownKey, UnseenKeys)) 308 Outer->warning("Unknown " + Description + " key '" + *UnknownKey + 309 "'; did you mean '" + *BestGuess + "'?", 310 UnknownKey.Range); 311 else 312 Outer->warning("Unknown " + Description + " key '" + *UnknownKey + 313 "'", 314 UnknownKey.Range); 315 } 316 }; 317 318 // Try to parse a single scalar value from the node, warn on failure. 319 llvm::Optional<Located<std::string>> scalarValue(Node &N, 320 llvm::StringRef Desc) { 321 llvm::SmallString<256> Buf; 322 if (auto *S = llvm::dyn_cast<ScalarNode>(&N)) 323 return Located<std::string>(S->getValue(Buf).str(), N.getSourceRange()); 324 if (auto *BS = llvm::dyn_cast<BlockScalarNode>(&N)) 325 return Located<std::string>(BS->getValue().str(), N.getSourceRange()); 326 warning(Desc + " should be scalar", N); 327 return llvm::None; 328 } 329 330 // Try to parse a list of single scalar values, or just a single value. 331 llvm::Optional<std::vector<Located<std::string>>> scalarValues(Node &N) { 332 std::vector<Located<std::string>> Result; 333 if (auto *S = llvm::dyn_cast<ScalarNode>(&N)) { 334 llvm::SmallString<256> Buf; 335 Result.emplace_back(S->getValue(Buf).str(), N.getSourceRange()); 336 } else if (auto *S = llvm::dyn_cast<BlockScalarNode>(&N)) { 337 Result.emplace_back(S->getValue().str(), N.getSourceRange()); 338 } else if (auto *S = llvm::dyn_cast<SequenceNode>(&N)) { 339 // We *must* consume all items, even on error, or the parser will assert. 340 for (auto &Child : *S) { 341 if (auto Value = scalarValue(Child, "List item")) 342 Result.push_back(std::move(*Value)); 343 } 344 } else { 345 warning("Expected scalar or list of scalars", N); 346 return llvm::None; 347 } 348 return Result; 349 } 350 351 // Report a "hard" error, reflecting a config file that can never be valid. 352 void error(const llvm::Twine &Msg, llvm::SMRange Range) { 353 HadError = true; 354 SM.PrintMessage(Range.Start, llvm::SourceMgr::DK_Error, Msg, Range); 355 } 356 void error(const llvm::Twine &Msg, const Node &N) { 357 return error(Msg, N.getSourceRange()); 358 } 359 360 // Report a "soft" error that could be caused by e.g. version skew. 361 void warning(const llvm::Twine &Msg, llvm::SMRange Range) { 362 SM.PrintMessage(Range.Start, llvm::SourceMgr::DK_Warning, Msg, Range); 363 } 364 void warning(const llvm::Twine &Msg, const Node &N) { 365 return warning(Msg, N.getSourceRange()); 366 } 367 }; 368 369 } // namespace 370 371 std::vector<Fragment> Fragment::parseYAML(llvm::StringRef YAML, 372 llvm::StringRef BufferName, 373 DiagnosticCallback Diags) { 374 // The YAML document may contain multiple conditional fragments. 375 // The SourceManager is shared for all of them. 376 auto SM = std::make_shared<llvm::SourceMgr>(); 377 auto Buf = llvm::MemoryBuffer::getMemBufferCopy(YAML, BufferName); 378 // Adapt DiagnosticCallback to function-pointer interface. 379 // Callback receives both errors we emit and those from the YAML parser. 380 SM->setDiagHandler( 381 [](const llvm::SMDiagnostic &Diag, void *Ctx) { 382 (*reinterpret_cast<DiagnosticCallback *>(Ctx))(Diag); 383 }, 384 &Diags); 385 std::vector<Fragment> Result; 386 for (auto &Doc : llvm::yaml::Stream(*Buf, *SM)) { 387 if (Node *N = Doc.getRoot()) { 388 Fragment Fragment; 389 Fragment.Source.Manager = SM; 390 Fragment.Source.Location = N->getSourceRange().Start; 391 SM->PrintMessage(Fragment.Source.Location, llvm::SourceMgr::DK_Note, 392 "Parsing config fragment"); 393 if (Parser(*SM).parse(Fragment, *N)) 394 Result.push_back(std::move(Fragment)); 395 } 396 } 397 SM->PrintMessage(SM->FindLocForLineAndColumn(SM->getMainFileID(), 0, 0), 398 llvm::SourceMgr::DK_Note, 399 "Parsed " + llvm::Twine(Result.size()) + 400 " fragments from file"); 401 // Hack: stash the buffer in the SourceMgr to keep it alive. 402 // SM has two entries: "main" non-owning buffer, and ignored owning buffer. 403 SM->AddNewSourceBuffer(std::move(Buf), llvm::SMLoc()); 404 return Result; 405 } 406 407 } // namespace config 408 } // namespace clangd 409 } // namespace clang 410