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