• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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