1//===--- JSONCompilationDatabase.cpp - ------------------------------------===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10//  This file contains the implementation of the JSONCompilationDatabase.
11//
12//===----------------------------------------------------------------------===//
13
14#include "clang/Tooling/JSONCompilationDatabase.h"
15#include "clang/Tooling/CompilationDatabase.h"
16#include "clang/Tooling/CompilationDatabasePluginRegistry.h"
17#include "clang/Tooling/Tooling.h"
18#include "llvm/ADT/SmallString.h"
19#include "llvm/Support/Path.h"
20#include "llvm/Support/system_error.h"
21
22namespace clang {
23namespace tooling {
24
25namespace {
26
27/// \brief A parser for escaped strings of command line arguments.
28///
29/// Assumes \-escaping for quoted arguments (see the documentation of
30/// unescapeCommandLine(...)).
31class CommandLineArgumentParser {
32 public:
33  CommandLineArgumentParser(StringRef CommandLine)
34      : Input(CommandLine), Position(Input.begin()-1) {}
35
36  std::vector<std::string> parse() {
37    bool HasMoreInput = true;
38    while (HasMoreInput && nextNonWhitespace()) {
39      std::string Argument;
40      HasMoreInput = parseStringInto(Argument);
41      CommandLine.push_back(Argument);
42    }
43    return CommandLine;
44  }
45
46 private:
47  // All private methods return true if there is more input available.
48
49  bool parseStringInto(std::string &String) {
50    do {
51      if (*Position == '"') {
52        if (!parseDoubleQuotedStringInto(String)) return false;
53      } else if (*Position == '\'') {
54        if (!parseSingleQuotedStringInto(String)) return false;
55      } else {
56        if (!parseFreeStringInto(String)) return false;
57      }
58    } while (*Position != ' ');
59    return true;
60  }
61
62  bool parseDoubleQuotedStringInto(std::string &String) {
63    if (!next()) return false;
64    while (*Position != '"') {
65      if (!skipEscapeCharacter()) return false;
66      String.push_back(*Position);
67      if (!next()) return false;
68    }
69    return next();
70  }
71
72  bool parseSingleQuotedStringInto(std::string &String) {
73    if (!next()) return false;
74    while (*Position != '\'') {
75      String.push_back(*Position);
76      if (!next()) return false;
77    }
78    return next();
79  }
80
81  bool parseFreeStringInto(std::string &String) {
82    do {
83      if (!skipEscapeCharacter()) return false;
84      String.push_back(*Position);
85      if (!next()) return false;
86    } while (*Position != ' ' && *Position != '"' && *Position != '\'');
87    return true;
88  }
89
90  bool skipEscapeCharacter() {
91    if (*Position == '\\') {
92      return next();
93    }
94    return true;
95  }
96
97  bool nextNonWhitespace() {
98    do {
99      if (!next()) return false;
100    } while (*Position == ' ');
101    return true;
102  }
103
104  bool next() {
105    ++Position;
106    return Position != Input.end();
107  }
108
109  const StringRef Input;
110  StringRef::iterator Position;
111  std::vector<std::string> CommandLine;
112};
113
114std::vector<std::string> unescapeCommandLine(
115    StringRef EscapedCommandLine) {
116  CommandLineArgumentParser parser(EscapedCommandLine);
117  return parser.parse();
118}
119
120class JSONCompilationDatabasePlugin : public CompilationDatabasePlugin {
121  virtual CompilationDatabase *loadFromDirectory(
122      StringRef Directory, std::string &ErrorMessage) {
123    SmallString<1024> JSONDatabasePath(Directory);
124    llvm::sys::path::append(JSONDatabasePath, "compile_commands.json");
125    OwningPtr<CompilationDatabase> Database(
126        JSONCompilationDatabase::loadFromFile(JSONDatabasePath, ErrorMessage));
127    if (!Database)
128      return NULL;
129    return Database.take();
130  }
131};
132
133} // end namespace
134
135// Register the JSONCompilationDatabasePlugin with the
136// CompilationDatabasePluginRegistry using this statically initialized variable.
137static CompilationDatabasePluginRegistry::Add<JSONCompilationDatabasePlugin>
138X("json-compilation-database", "Reads JSON formatted compilation databases");
139
140// This anchor is used to force the linker to link in the generated object file
141// and thus register the JSONCompilationDatabasePlugin.
142volatile int JSONAnchorSource = 0;
143
144JSONCompilationDatabase *
145JSONCompilationDatabase::loadFromFile(StringRef FilePath,
146                                      std::string &ErrorMessage) {
147  OwningPtr<llvm::MemoryBuffer> DatabaseBuffer;
148  llvm::error_code Result =
149    llvm::MemoryBuffer::getFile(FilePath, DatabaseBuffer);
150  if (Result != 0) {
151    ErrorMessage = "Error while opening JSON database: " + Result.message();
152    return NULL;
153  }
154  OwningPtr<JSONCompilationDatabase> Database(
155    new JSONCompilationDatabase(DatabaseBuffer.take()));
156  if (!Database->parse(ErrorMessage))
157    return NULL;
158  return Database.take();
159}
160
161JSONCompilationDatabase *
162JSONCompilationDatabase::loadFromBuffer(StringRef DatabaseString,
163                                        std::string &ErrorMessage) {
164  OwningPtr<llvm::MemoryBuffer> DatabaseBuffer(
165      llvm::MemoryBuffer::getMemBuffer(DatabaseString));
166  OwningPtr<JSONCompilationDatabase> Database(
167      new JSONCompilationDatabase(DatabaseBuffer.take()));
168  if (!Database->parse(ErrorMessage))
169    return NULL;
170  return Database.take();
171}
172
173std::vector<CompileCommand>
174JSONCompilationDatabase::getCompileCommands(StringRef FilePath) const {
175  SmallString<128> NativeFilePath;
176  llvm::sys::path::native(FilePath, NativeFilePath);
177  std::vector<StringRef> PossibleMatches;
178  std::string Error;
179  llvm::raw_string_ostream ES(Error);
180  StringRef Match = MatchTrie.findEquivalent(NativeFilePath.str(), ES);
181  if (Match.empty())
182    return std::vector<CompileCommand>();
183  llvm::StringMap< std::vector<CompileCommandRef> >::const_iterator
184    CommandsRefI = IndexByFile.find(Match);
185  if (CommandsRefI == IndexByFile.end())
186    return std::vector<CompileCommand>();
187  std::vector<CompileCommand> Commands;
188  getCommands(CommandsRefI->getValue(), Commands);
189  return Commands;
190}
191
192std::vector<std::string>
193JSONCompilationDatabase::getAllFiles() const {
194  std::vector<std::string> Result;
195
196  llvm::StringMap< std::vector<CompileCommandRef> >::const_iterator
197    CommandsRefI = IndexByFile.begin();
198  const llvm::StringMap< std::vector<CompileCommandRef> >::const_iterator
199    CommandsRefEnd = IndexByFile.end();
200  for (; CommandsRefI != CommandsRefEnd; ++CommandsRefI) {
201    Result.push_back(CommandsRefI->first().str());
202  }
203
204  return Result;
205}
206
207std::vector<CompileCommand>
208JSONCompilationDatabase::getAllCompileCommands() const {
209  std::vector<CompileCommand> Commands;
210  for (llvm::StringMap< std::vector<CompileCommandRef> >::const_iterator
211        CommandsRefI = IndexByFile.begin(), CommandsRefEnd = IndexByFile.end();
212      CommandsRefI != CommandsRefEnd; ++CommandsRefI) {
213    getCommands(CommandsRefI->getValue(), Commands);
214  }
215  return Commands;
216}
217
218void JSONCompilationDatabase::getCommands(
219                                  ArrayRef<CompileCommandRef> CommandsRef,
220                                  std::vector<CompileCommand> &Commands) const {
221  for (int I = 0, E = CommandsRef.size(); I != E; ++I) {
222    SmallString<8> DirectoryStorage;
223    SmallString<1024> CommandStorage;
224    Commands.push_back(CompileCommand(
225      // FIXME: Escape correctly:
226      CommandsRef[I].first->getValue(DirectoryStorage),
227      unescapeCommandLine(CommandsRef[I].second->getValue(CommandStorage))));
228  }
229}
230
231bool JSONCompilationDatabase::parse(std::string &ErrorMessage) {
232  llvm::yaml::document_iterator I = YAMLStream.begin();
233  if (I == YAMLStream.end()) {
234    ErrorMessage = "Error while parsing YAML.";
235    return false;
236  }
237  llvm::yaml::Node *Root = I->getRoot();
238  if (Root == NULL) {
239    ErrorMessage = "Error while parsing YAML.";
240    return false;
241  }
242  llvm::yaml::SequenceNode *Array = dyn_cast<llvm::yaml::SequenceNode>(Root);
243  if (Array == NULL) {
244    ErrorMessage = "Expected array.";
245    return false;
246  }
247  for (llvm::yaml::SequenceNode::iterator AI = Array->begin(),
248                                          AE = Array->end();
249       AI != AE; ++AI) {
250    llvm::yaml::MappingNode *Object = dyn_cast<llvm::yaml::MappingNode>(&*AI);
251    if (Object == NULL) {
252      ErrorMessage = "Expected object.";
253      return false;
254    }
255    llvm::yaml::ScalarNode *Directory = NULL;
256    llvm::yaml::ScalarNode *Command = NULL;
257    llvm::yaml::ScalarNode *File = NULL;
258    for (llvm::yaml::MappingNode::iterator KVI = Object->begin(),
259                                           KVE = Object->end();
260         KVI != KVE; ++KVI) {
261      llvm::yaml::Node *Value = (*KVI).getValue();
262      if (Value == NULL) {
263        ErrorMessage = "Expected value.";
264        return false;
265      }
266      llvm::yaml::ScalarNode *ValueString =
267          dyn_cast<llvm::yaml::ScalarNode>(Value);
268      if (ValueString == NULL) {
269        ErrorMessage = "Expected string as value.";
270        return false;
271      }
272      llvm::yaml::ScalarNode *KeyString =
273          dyn_cast<llvm::yaml::ScalarNode>((*KVI).getKey());
274      if (KeyString == NULL) {
275        ErrorMessage = "Expected strings as key.";
276        return false;
277      }
278      SmallString<8> KeyStorage;
279      if (KeyString->getValue(KeyStorage) == "directory") {
280        Directory = ValueString;
281      } else if (KeyString->getValue(KeyStorage) == "command") {
282        Command = ValueString;
283      } else if (KeyString->getValue(KeyStorage) == "file") {
284        File = ValueString;
285      } else {
286        ErrorMessage = ("Unknown key: \"" +
287                        KeyString->getRawValue() + "\"").str();
288        return false;
289      }
290    }
291    if (!File) {
292      ErrorMessage = "Missing key: \"file\".";
293      return false;
294    }
295    if (!Command) {
296      ErrorMessage = "Missing key: \"command\".";
297      return false;
298    }
299    if (!Directory) {
300      ErrorMessage = "Missing key: \"directory\".";
301      return false;
302    }
303    SmallString<8> FileStorage;
304    StringRef FileName = File->getValue(FileStorage);
305    SmallString<128> NativeFilePath;
306    if (llvm::sys::path::is_relative(FileName)) {
307      SmallString<8> DirectoryStorage;
308      SmallString<128> AbsolutePath(
309          Directory->getValue(DirectoryStorage));
310      llvm::sys::path::append(AbsolutePath, FileName);
311      llvm::sys::path::native(AbsolutePath.str(), NativeFilePath);
312    } else {
313      llvm::sys::path::native(FileName, NativeFilePath);
314    }
315    IndexByFile[NativeFilePath].push_back(
316        CompileCommandRef(Directory, Command));
317    MatchTrie.insert(NativeFilePath.str());
318  }
319  return true;
320}
321
322} // end namespace tooling
323} // end namespace clang
324