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 "llvm/ADT/SmallString.h"
16 #include "llvm/Support/JSONParser.h"
17 #include "llvm/Support/Path.h"
18 #include "llvm/Support/system_error.h"
19 
20 namespace clang {
21 namespace tooling {
22 
23 namespace {
24 
25 /// \brief A parser for JSON escaped strings of command line arguments.
26 ///
27 /// Assumes \-escaping for quoted arguments (see the documentation of
28 /// unescapeJSONCommandLine(...)).
29 class CommandLineArgumentParser {
30  public:
31   CommandLineArgumentParser(StringRef CommandLine)
32       : Input(CommandLine), Position(Input.begin()-1) {}
33 
34   std::vector<std::string> parse() {
35     bool HasMoreInput = true;
36     while (HasMoreInput && nextNonWhitespace()) {
37       std::string Argument;
38       HasMoreInput = parseStringInto(Argument);
39       CommandLine.push_back(Argument);
40     }
41     return CommandLine;
42   }
43 
44  private:
45   // All private methods return true if there is more input available.
46 
47   bool parseStringInto(std::string &String) {
48     do {
49       if (*Position == '"') {
50         if (!parseQuotedStringInto(String)) return false;
51       } else {
52         if (!parseFreeStringInto(String)) return false;
53       }
54     } while (*Position != ' ');
55     return true;
56   }
57 
58   bool parseQuotedStringInto(std::string &String) {
59     if (!next()) return false;
60     while (*Position != '"') {
61       if (!skipEscapeCharacter()) return false;
62       String.push_back(*Position);
63       if (!next()) return false;
64     }
65     return next();
66   }
67 
68   bool parseFreeStringInto(std::string &String) {
69     do {
70       if (!skipEscapeCharacter()) return false;
71       String.push_back(*Position);
72       if (!next()) return false;
73     } while (*Position != ' ' && *Position != '"');
74     return true;
75   }
76 
77   bool skipEscapeCharacter() {
78     if (*Position == '\\') {
79       return next();
80     }
81     return true;
82   }
83 
84   bool nextNonWhitespace() {
85     do {
86       if (!next()) return false;
87     } while (*Position == ' ');
88     return true;
89   }
90 
91   bool next() {
92     ++Position;
93     if (Position == Input.end()) return false;
94     // Remove the JSON escaping first. This is done unconditionally.
95     if (*Position == '\\') ++Position;
96     return Position != Input.end();
97   }
98 
99   const StringRef Input;
100   StringRef::iterator Position;
101   std::vector<std::string> CommandLine;
102 };
103 
104 std::vector<std::string> unescapeJSONCommandLine(
105     StringRef JSONEscapedCommandLine) {
106   CommandLineArgumentParser parser(JSONEscapedCommandLine);
107   return parser.parse();
108 }
109 
110 } // end namespace
111 
112 CompilationDatabase::~CompilationDatabase() {}
113 
114 CompilationDatabase *
115 CompilationDatabase::loadFromDirectory(StringRef BuildDirectory,
116                                        std::string &ErrorMessage) {
117   llvm::SmallString<1024> JSONDatabasePath(BuildDirectory);
118   llvm::sys::path::append(JSONDatabasePath, "compile_commands.json");
119   llvm::OwningPtr<CompilationDatabase> Database(
120     JSONCompilationDatabase::loadFromFile(JSONDatabasePath, ErrorMessage));
121   if (!Database) {
122     return NULL;
123   }
124   return Database.take();
125 }
126 
127 JSONCompilationDatabase *
128 JSONCompilationDatabase::loadFromFile(StringRef FilePath,
129                                       std::string &ErrorMessage) {
130   llvm::OwningPtr<llvm::MemoryBuffer> DatabaseBuffer;
131   llvm::error_code Result =
132     llvm::MemoryBuffer::getFile(FilePath, DatabaseBuffer);
133   if (Result != 0) {
134     ErrorMessage = "Error while opening JSON database: " + Result.message();
135     return NULL;
136   }
137   llvm::OwningPtr<JSONCompilationDatabase> Database(
138     new JSONCompilationDatabase(DatabaseBuffer.take()));
139   if (!Database->parse(ErrorMessage))
140     return NULL;
141   return Database.take();
142 }
143 
144 JSONCompilationDatabase *
145 JSONCompilationDatabase::loadFromBuffer(StringRef DatabaseString,
146                                         std::string &ErrorMessage) {
147   llvm::OwningPtr<llvm::MemoryBuffer> DatabaseBuffer(
148       llvm::MemoryBuffer::getMemBuffer(DatabaseString));
149   llvm::OwningPtr<JSONCompilationDatabase> Database(
150     new JSONCompilationDatabase(DatabaseBuffer.take()));
151   if (!Database->parse(ErrorMessage))
152     return NULL;
153   return Database.take();
154 }
155 
156 std::vector<CompileCommand>
157 JSONCompilationDatabase::getCompileCommands(StringRef FilePath) const {
158   llvm::StringMap< std::vector<CompileCommandRef> >::const_iterator
159     CommandsRefI = IndexByFile.find(FilePath);
160   if (CommandsRefI == IndexByFile.end())
161     return std::vector<CompileCommand>();
162   const std::vector<CompileCommandRef> &CommandsRef = CommandsRefI->getValue();
163   std::vector<CompileCommand> Commands;
164   for (int I = 0, E = CommandsRef.size(); I != E; ++I) {
165     Commands.push_back(CompileCommand(
166       // FIXME: Escape correctly:
167       CommandsRef[I].first,
168       unescapeJSONCommandLine(CommandsRef[I].second)));
169   }
170   return Commands;
171 }
172 
173 bool JSONCompilationDatabase::parse(std::string &ErrorMessage) {
174   llvm::SourceMgr SM;
175   llvm::JSONParser Parser(Database->getBuffer(), &SM);
176   llvm::JSONValue *Root = Parser.parseRoot();
177   if (Root == NULL) {
178     ErrorMessage = "Error while parsing JSON.";
179     return false;
180   }
181   llvm::JSONArray *Array = dyn_cast<llvm::JSONArray>(Root);
182   if (Array == NULL) {
183     ErrorMessage = "Expected array.";
184     return false;
185   }
186   for (llvm::JSONArray::const_iterator AI = Array->begin(), AE = Array->end();
187        AI != AE; ++AI) {
188     const llvm::JSONObject *Object = dyn_cast<llvm::JSONObject>(*AI);
189     if (Object == NULL) {
190       ErrorMessage = "Expected object.";
191       return false;
192     }
193     StringRef EntryDirectory;
194     StringRef EntryFile;
195     StringRef EntryCommand;
196     for (llvm::JSONObject::const_iterator KVI = Object->begin(),
197                                           KVE = Object->end();
198          KVI != KVE; ++KVI) {
199       const llvm::JSONValue *Value = (*KVI)->Value;
200       if (Value == NULL) {
201         ErrorMessage = "Expected value.";
202         return false;
203       }
204       const llvm::JSONString *ValueString =
205         dyn_cast<llvm::JSONString>(Value);
206       if (ValueString == NULL) {
207         ErrorMessage = "Expected string as value.";
208         return false;
209       }
210       if ((*KVI)->Key->getRawText() == "directory") {
211         EntryDirectory = ValueString->getRawText();
212       } else if ((*KVI)->Key->getRawText() == "file") {
213         EntryFile = ValueString->getRawText();
214       } else if ((*KVI)->Key->getRawText() == "command") {
215         EntryCommand = ValueString->getRawText();
216       } else {
217         ErrorMessage = (Twine("Unknown key: \"") +
218                         (*KVI)->Key->getRawText() + "\"").str();
219         return false;
220       }
221     }
222     IndexByFile[EntryFile].push_back(
223       CompileCommandRef(EntryDirectory, EntryCommand));
224   }
225   return true;
226 }
227 
228 } // end namespace tooling
229 } // end namespace clang
230 
231