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