• 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/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