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