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