1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "sandboxed_api/tools/clang_generator/compilation_database.h"
16
17 #include <algorithm>
18 #include <memory>
19 #include <string>
20 #include <utility>
21 #include <vector>
22
23 #include "absl/strings/string_view.h"
24 #include "absl/strings/strip.h"
25 #include "clang/Driver/Types.h"
26 #include "clang/Tooling/ArgumentsAdjusters.h"
27 #include "clang/Tooling/CommonOptionsParser.h"
28 #include "clang/Tooling/CompilationDatabase.h"
29 #include "llvm/Config/llvm-config.h"
30 #include "llvm/Support/CommandLine.h"
31 #include "llvm/Support/Error.h"
32 #include "llvm/Support/Path.h"
33 #include "llvm/Support/raw_ostream.h"
34
35 namespace sapi {
36
GetAllSubCommands()37 static llvm::cl::SubCommand& GetAllSubCommands() {
38 #if LLVM_VERSION_MAJOR >= 15
39 return llvm::cl::SubCommand::getAll();
40 #else
41 return *llvm::cl::AllSubCommands;
42 #endif
43 }
44
45 class WrappingCompilationDatabase : public clang::tooling::CompilationDatabase {
46 public:
WrappingCompilationDatabase(clang::tooling::CompilationDatabase & inner)47 explicit WrappingCompilationDatabase(
48 clang::tooling::CompilationDatabase& inner)
49 : inner_(&inner) {}
50
51 private:
getCompileCommands(llvm::StringRef file_path) const52 std::vector<clang::tooling::CompileCommand> getCompileCommands(
53 llvm::StringRef file_path) const override {
54 return inner_->getCompileCommands(file_path);
55 }
56
getAllFiles() const57 std::vector<std::string> getAllFiles() const override {
58 return inner_->getAllFiles();
59 }
60
getAllCompileCommands() const61 std::vector<clang::tooling::CompileCommand> getAllCompileCommands()
62 const override {
63 return inner_->getAllCompileCommands();
64 }
65
66 clang::tooling::CompilationDatabase* inner_;
67 };
68
NonOwningCompileCommands(clang::tooling::CompilationDatabase & inner)69 std::unique_ptr<clang::tooling::CompilationDatabase> NonOwningCompileCommands(
70 clang::tooling::CompilationDatabase& inner) {
71 return std::make_unique<WrappingCompilationDatabase>(inner);
72 }
73
74 // Returns the command-line argument for setting the highest C language standard
75 // version for a given C++ standard version. If the specified string does not
76 // indicate a C++ standard, it is returned unchanged.
CxxStdToCStd(const std::string & arg)77 std::string CxxStdToCStd(const std::string& arg) {
78 absl::string_view std = arg;
79 if (!absl::ConsumePrefix(&std, "--std=c++") &&
80 !absl::ConsumePrefix(&std, "-std=c++")) {
81 return arg;
82 }
83 if (std == "23" || std == "2z" || std == "20" || std == "2a") {
84 return "--std=c17";
85 }
86 if (std == "17" || std == "1z" || std == "14" || std == "1y") {
87 return "--std=c11";
88 }
89 if (std == "11" || std == "0x") {
90 return "--std=c99";
91 }
92 return "--std=c89";
93 }
94
95 class FromCxxAjustedCompilationDatabase
96 : public clang::tooling::CompilationDatabase {
97 public:
FromCxxAjustedCompilationDatabase(std::unique_ptr<clang::tooling::CompilationDatabase> inner)98 explicit FromCxxAjustedCompilationDatabase(
99 std::unique_ptr<clang::tooling::CompilationDatabase> inner)
100 : inner_(std::move(inner)) {}
101
getCompileCommands(llvm::StringRef file_path) const102 std::vector<clang::tooling::CompileCommand> getCompileCommands(
103 llvm::StringRef file_path) const override {
104 clang::driver::types::ID id =
105 llvm::sys::path::has_extension(file_path)
106 ? clang::driver::types::lookupTypeForExtension(
107 llvm::sys::path::extension(file_path).drop_front())
108 : clang::driver::types::TY_CXXHeader;
109
110 std::vector<clang::tooling::CompileCommand> cmds =
111 inner_->getCompileCommands(file_path);
112 for (auto& cmd : cmds) {
113 auto& argv = cmd.CommandLine;
114 if (clang::driver::types::isCXX(id) ||
115 id == clang::driver::types::TY_CHeader) {
116 argv[0] = "clang++";
117 if (id == clang::driver::types::TY_CHeader) {
118 // Parse all headers as C++. Well-behaved headers should have an
119 // include guard.
120 argv.insert(argv.begin() + 1, {"-x", "c++"});
121 }
122 } else {
123 argv[0] = "clang";
124 std::transform(argv.begin(), argv.end(), argv.begin(), CxxStdToCStd);
125 }
126 }
127 return cmds;
128 }
129
getAllFiles() const130 std::vector<std::string> getAllFiles() const override {
131 return inner_->getAllFiles();
132 }
133
getAllCompileCommands() const134 std::vector<clang::tooling::CompileCommand> getAllCompileCommands()
135 const override {
136 return {};
137 }
138
139 std::unique_ptr<clang::tooling::CompilationDatabase> inner_;
140 };
141
142 std::unique_ptr<clang::tooling::CompilationDatabase>
FromCxxAjustedCompileCommands(std::unique_ptr<clang::tooling::CompilationDatabase> inner)143 FromCxxAjustedCompileCommands(
144 std::unique_ptr<clang::tooling::CompilationDatabase> inner) {
145 return std::make_unique<FromCxxAjustedCompilationDatabase>(std::move(inner));
146 }
147
create(int & argc,const char ** argv,llvm::cl::OptionCategory & category,llvm::cl::NumOccurrencesFlag occurrences_flag,const char * overview)148 llvm::Expected<OptionsParser> OptionsParser::create(
149 int& argc, const char** argv, llvm::cl::OptionCategory& category,
150 llvm::cl::NumOccurrencesFlag occurrences_flag, const char* overview) {
151 OptionsParser parser;
152 if (llvm::Error err =
153 parser.init(argc, argv, category, occurrences_flag, overview);
154 err) {
155 return err;
156 }
157 return parser;
158 }
159
init(int & argc,const char ** argv,llvm::cl::OptionCategory & category,llvm::cl::NumOccurrencesFlag occurrences_flag,const char * overview)160 llvm::Error OptionsParser::init(int& argc, const char** argv,
161 llvm::cl::OptionCategory& category,
162 llvm::cl::NumOccurrencesFlag occurrences_flag,
163 const char* overview) {
164 static auto* build_path = new llvm::cl::opt<std::string>(
165 "p", llvm::cl::desc("Build path"), llvm::cl::Optional,
166 llvm::cl::cat(category), llvm::cl::sub(GetAllSubCommands()));
167
168 static auto* source_paths = new llvm::cl::list<std::string>(
169 llvm::cl::Positional, llvm::cl::desc("<source0> [... <sourceN>]"),
170 occurrences_flag, llvm::cl::cat(category),
171 llvm::cl::sub(GetAllSubCommands()));
172
173 static auto* args_after = new llvm::cl::list<std::string>(
174 "extra-arg",
175 llvm::cl::desc(
176 "Additional argument to append to the compiler command line"),
177 llvm::cl::cat(category), llvm::cl::sub(GetAllSubCommands()));
178
179 static auto* args_before = new llvm::cl::list<std::string>(
180 "extra-arg-before",
181 llvm::cl::desc(
182 "Additional argument to prepend to the compiler command line"),
183 llvm::cl::cat(category), llvm::cl::sub(GetAllSubCommands()));
184
185 llvm::cl::ResetAllOptionOccurrences();
186
187 llvm::cl::HideUnrelatedOptions(category);
188
189 {
190 std::string error_message;
191 compilations_ =
192 clang::tooling::FixedCompilationDatabase::loadFromCommandLine(
193 argc, argv, error_message);
194 if (!error_message.empty()) {
195 error_message.append("\n");
196 }
197
198 // Stop initializing if command-line option parsing failed.
199 if (llvm::raw_string_ostream os(error_message);
200 !llvm::cl::ParseCommandLineOptions(argc, argv, overview, &os)) {
201 os.flush();
202 return llvm::make_error<llvm::StringError>(
203 error_message, llvm::inconvertibleErrorCode());
204 }
205 }
206 llvm::cl::PrintOptionValues();
207
208 source_path_list_ = *source_paths;
209 if ((occurrences_flag == llvm::cl::ZeroOrMore ||
210 occurrences_flag == llvm::cl::Optional) &&
211 source_path_list_.empty()) {
212 return llvm::Error::success();
213 }
214 if (!compilations_) {
215 std::string error_message;
216 if (!build_path->empty()) {
217 compilations_ =
218 clang::tooling::CompilationDatabase::autoDetectFromDirectory(
219 *build_path, error_message);
220 } else {
221 compilations_ = clang::tooling::CompilationDatabase::autoDetectFromSource(
222 (*source_paths)[0], error_message);
223 }
224 if (!compilations_) {
225 compilations_.reset(new clang::tooling::FixedCompilationDatabase(
226 ".", std::vector<std::string>()));
227 }
228 }
229 auto adjusting_compilations =
230 std::make_unique<clang::tooling::ArgumentsAdjustingCompilations>(
231 std::move(compilations_));
232 adjuster_ = getInsertArgumentAdjuster(
233 *args_before, clang::tooling::ArgumentInsertPosition::BEGIN);
234 adjuster_ = clang::tooling::combineAdjusters(
235 std::move(adjuster_),
236 getInsertArgumentAdjuster(*args_after,
237 clang::tooling::ArgumentInsertPosition::END));
238 adjusting_compilations->appendArgumentsAdjuster(adjuster_);
239 compilations_ = std::move(adjusting_compilations);
240 return llvm::Error::success();
241 }
242
243 } // namespace sapi
244