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/CompilationDatabase.h"
27 #include "llvm/Support/Path.h"
28
29 namespace sapi {
30
31 class WrappingCompilationDatabase : public clang::tooling::CompilationDatabase {
32 public:
WrappingCompilationDatabase(clang::tooling::CompilationDatabase & inner)33 explicit WrappingCompilationDatabase(
34 clang::tooling::CompilationDatabase& inner)
35 : inner_(&inner) {}
36
37 private:
getCompileCommands(llvm::StringRef file_path) const38 std::vector<clang::tooling::CompileCommand> getCompileCommands(
39 llvm::StringRef file_path) const override {
40 return inner_->getCompileCommands(file_path);
41 }
42
getAllFiles() const43 std::vector<std::string> getAllFiles() const override {
44 return inner_->getAllFiles();
45 }
46
getAllCompileCommands() const47 std::vector<clang::tooling::CompileCommand> getAllCompileCommands()
48 const override {
49 return inner_->getAllCompileCommands();
50 }
51
52 clang::tooling::CompilationDatabase* inner_;
53 };
54
NonOwningCompileCommands(clang::tooling::CompilationDatabase & inner)55 std::unique_ptr<clang::tooling::CompilationDatabase> NonOwningCompileCommands(
56 clang::tooling::CompilationDatabase& inner) {
57 return std::make_unique<WrappingCompilationDatabase>(inner);
58 }
59
60 // Returns the command-line argument for setting the highest C language standard
61 // version for a given C++ standard version. If the specified string does not
62 // indicate a C++ standard, it is returned unchanged.
CxxStdToCStd(const std::string & arg)63 std::string CxxStdToCStd(const std::string& arg) {
64 absl::string_view std = arg;
65 if (!absl::ConsumePrefix(&std, "--std=c++") &&
66 !absl::ConsumePrefix(&std, "-std=c++")) {
67 return arg;
68 }
69 if (std == "23" || std == "2z" || std == "20" || std == "2a") {
70 return "--std=c17";
71 }
72 if (std == "17" || std == "1z" || std == "14" || std == "1y") {
73 return "--std=c11";
74 }
75 if (std == "11" || std == "0x") {
76 return "--std=c99";
77 }
78 return "--std=c89";
79 }
80
81 class FromCxxAjustedCompilationDatabase
82 : public clang::tooling::CompilationDatabase {
83 public:
FromCxxAjustedCompilationDatabase(std::unique_ptr<clang::tooling::CompilationDatabase> inner)84 explicit FromCxxAjustedCompilationDatabase(
85 std::unique_ptr<clang::tooling::CompilationDatabase> inner)
86 : inner_(std::move(inner)) {}
87
getCompileCommands(llvm::StringRef file_path) const88 std::vector<clang::tooling::CompileCommand> getCompileCommands(
89 llvm::StringRef file_path) const override {
90 clang::driver::types::ID id =
91 llvm::sys::path::has_extension(file_path)
92 ? clang::driver::types::lookupTypeForExtension(
93 llvm::sys::path::extension(file_path).drop_front())
94 : clang::driver::types::TY_CXXHeader;
95
96 std::vector<clang::tooling::CompileCommand> cmds =
97 inner_->getCompileCommands(file_path);
98 for (auto& cmd : cmds) {
99 auto& argv = cmd.CommandLine;
100 if (clang::driver::types::isCXX(id) ||
101 id == clang::driver::types::TY_CHeader) {
102 argv[0] = "clang++";
103 if (id == clang::driver::types::TY_CHeader) {
104 // Parse all headers as C++. Well-behaved headers should have an
105 // include guard.
106 argv.insert(argv.begin() + 1, {"-x", "c++"});
107 }
108 } else {
109 argv[0] = "clang";
110 std::transform(argv.begin(), argv.end(), argv.begin(), CxxStdToCStd);
111 }
112 }
113 return cmds;
114 }
115
getAllFiles() const116 std::vector<std::string> getAllFiles() const override {
117 return inner_->getAllFiles();
118 }
119
getAllCompileCommands() const120 std::vector<clang::tooling::CompileCommand> getAllCompileCommands()
121 const override {
122 return {};
123 }
124
125 std::unique_ptr<clang::tooling::CompilationDatabase> inner_;
126 };
127
128 std::unique_ptr<clang::tooling::CompilationDatabase>
FromCxxAjustedCompileCommands(std::unique_ptr<clang::tooling::CompilationDatabase> inner)129 FromCxxAjustedCompileCommands(
130 std::unique_ptr<clang::tooling::CompilationDatabase> inner) {
131 return std::make_unique<FromCxxAjustedCompilationDatabase>(std::move(inner));
132 }
133
create(int & argc,const char ** argv,llvm::cl::OptionCategory & category,llvm::cl::NumOccurrencesFlag occurrences_flag,const char * overview)134 llvm::Expected<OptionsParser> OptionsParser::create(
135 int& argc, const char** argv, llvm::cl::OptionCategory& category,
136 llvm::cl::NumOccurrencesFlag occurrences_flag, const char* overview) {
137 OptionsParser parser;
138 if (llvm::Error err =
139 parser.init(argc, argv, category, occurrences_flag, overview);
140 err) {
141 return err;
142 }
143 return parser;
144 }
145
init(int & argc,const char ** argv,llvm::cl::OptionCategory & category,llvm::cl::NumOccurrencesFlag occurrences_flag,const char * overview)146 llvm::Error OptionsParser::init(int& argc, const char** argv,
147 llvm::cl::OptionCategory& category,
148 llvm::cl::NumOccurrencesFlag occurrences_flag,
149 const char* overview) {
150 static auto* build_path = new llvm::cl::opt<std::string>(
151 "p", llvm::cl::desc("Build path"), llvm::cl::Optional,
152 llvm::cl::cat(category), llvm::cl::sub(*llvm::cl::AllSubCommands));
153
154 static auto* source_paths = new llvm::cl::list<std::string>(
155 llvm::cl::Positional, llvm::cl::desc("<source0> [... <sourceN>]"),
156 occurrences_flag, llvm::cl::cat(category),
157 llvm::cl::sub(*llvm::cl::AllSubCommands));
158
159 static auto* args_after = new llvm::cl::list<std::string>(
160 "extra-arg",
161 llvm::cl::desc(
162 "Additional argument to append to the compiler command line"),
163 llvm::cl::cat(category), llvm::cl::sub(*llvm::cl::AllSubCommands));
164
165 static auto* args_before = new llvm::cl::list<std::string>(
166 "extra-arg-before",
167 llvm::cl::desc(
168 "Additional argument to prepend to the compiler command line"),
169 llvm::cl::cat(category), llvm::cl::sub(*llvm::cl::AllSubCommands));
170
171 llvm::cl::ResetAllOptionOccurrences();
172
173 llvm::cl::HideUnrelatedOptions(category);
174
175 {
176 std::string error_message;
177 compilations_ =
178 clang::tooling::FixedCompilationDatabase::loadFromCommandLine(
179 argc, argv, error_message);
180 if (!error_message.empty()) {
181 error_message.append("\n");
182 }
183
184 // Stop initializing if command-line option parsing failed.
185 if (llvm::raw_string_ostream os(error_message);
186 !llvm::cl::ParseCommandLineOptions(argc, argv, overview, &os)) {
187 os.flush();
188 return llvm::make_error<llvm::StringError>(
189 error_message, llvm::inconvertibleErrorCode());
190 }
191 }
192 llvm::cl::PrintOptionValues();
193
194 source_path_list_ = *source_paths;
195 if ((occurrences_flag == llvm::cl::ZeroOrMore ||
196 occurrences_flag == llvm::cl::Optional) &&
197 source_path_list_.empty()) {
198 return llvm::Error::success();
199 }
200 if (!compilations_) {
201 std::string error_message;
202 if (!build_path->empty()) {
203 compilations_ =
204 clang::tooling::CompilationDatabase::autoDetectFromDirectory(
205 *build_path, error_message);
206 } else {
207 compilations_ = clang::tooling::CompilationDatabase::autoDetectFromSource(
208 (*source_paths)[0], error_message);
209 }
210 if (!compilations_) {
211 compilations_.reset(new clang::tooling::FixedCompilationDatabase(
212 ".", std::vector<std::string>()));
213 }
214 }
215 auto adjusting_compilations =
216 std::make_unique<clang::tooling::ArgumentsAdjustingCompilations>(
217 std::move(compilations_));
218 adjuster_ = getInsertArgumentAdjuster(
219 *args_before, clang::tooling::ArgumentInsertPosition::BEGIN);
220 adjuster_ = clang::tooling::combineAdjusters(
221 std::move(adjuster_),
222 getInsertArgumentAdjuster(*args_after,
223 clang::tooling::ArgumentInsertPosition::END));
224 adjusting_compilations->appendArgumentsAdjuster(adjuster_);
225 compilations_ = std::move(adjusting_compilations);
226 return llvm::Error::success();
227 }
228
229 } // namespace sapi
230