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