1212#include " llvm/ADT/SmallPtrSet.h"
1313
1414#include < algorithm>
15+ #include < array>
16+ #include < cstdint>
1517#include < cstdlib>
16- #include < iostream >
18+ #include < memory >
1719#include < optional>
20+ #include < set>
1821#include < string>
1922#include < tuple>
2023#include < unordered_map>
24+ #include < utility>
2125#include < vector>
2226
2327namespace idt {
@@ -63,11 +67,23 @@ ignored_symbols("ignore",
6367 llvm::cl::CommaSeparated,
6468 llvm::cl::cat(idt::category));
6569
70+ llvm::cl::opt<std::string>
71+ main_file (" main-file" ,
72+ llvm::cl::desc (" Path of a translation unit with an entry in the "
73+ " compilation database whose flags will be used to "
74+ " parse the positional argument" ),
75+ llvm::cl::value_desc(" path" ), llvm::cl::cat(idt::category));
76+
6677template <typename Key, typename Compare, typename Allocator>
6778bool contains (const std::set<Key, Compare, Allocator>& set, const Key& key) {
6879 return set.find (key) != set.end ();
6980}
7081
82+ template <class T , std::size_t N>
83+ bool contains (const std::array<T, N>& arr, const T& value) {
84+ return std::find (arr.begin (), arr.end (), value) != arr.end ();
85+ }
86+
7187const std::set<std::string> &get_ignored_symbols () {
7288 static auto kIgnoredFunctions = [&]() -> std::set<std::string> {
7389 return { ignored_symbols.begin (), ignored_symbols.end () };
@@ -271,6 +287,16 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
271287 return false ;
272288 }
273289
290+ // When `--main-file` is set, the positional argument is parsed as the
291+ // translation unit's main file; restrict fixits to it so we don't
292+ // accidentally rewrite decls in transitively-included headers. Outside
293+ // of `--main-file` mode, this is a no-op.
294+ template <typename Decl_> bool is_in_main_tu (const Decl_ *D) const {
295+ if (main_file.empty ())
296+ return true ;
297+ return source_manager_.isInMainFile (get_location (D));
298+ }
299+
274300 template <typename Decl_>
275301 inline bool is_in_system_header (const Decl_ *D) const {
276302 return source_manager_.isInSystemHeader (get_location (D));
@@ -347,6 +373,10 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
347373 if (!is_in_header (FD ))
348374 return ;
349375
376+ // Restrict to the main TU when `--main-file` is set.
377+ if (!is_in_main_tu (FD ))
378+ return ;
379+
350380 // Ignore friend declarations.
351381 if (FD ->getFriendObjectKind () != clang::Decl::FOK_None)
352382 return ;
@@ -431,6 +461,10 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
431461 if (!is_in_header (VD ))
432462 return ;
433463
464+ // Restrict to the main TU when `--main-file` is set.
465+ if (!is_in_main_tu (VD ))
466+ return ;
467+
434468 // Skip local variables. We are only interested in static fields.
435469 if (VD ->getParentFunctionOrMethod ())
436470 return ;
@@ -501,6 +535,10 @@ class visitor : public clang::RecursiveASTVisitor<visitor> {
501535 if (is_in_system_header (RD ))
502536 return ;
503537
538+ // Restrict to the main TU when `--main-file` is set.
539+ if (!is_in_main_tu (RD ))
540+ return ;
541+
504542 // Skip exporting template classes. For fully-specialized template classes,
505543 // isTemplated() returns false so they will be annotated if needed.
506544 if (RD ->isTemplated ())
@@ -737,6 +775,59 @@ struct factory : clang::tooling::FrontendActionFactory {
737775 return std::make_unique<idt::action>();
738776 }
739777};
778+
779+ // CompilationDatabase wrapper that looks up flags under one path (the
780+ // "donor") and reports them as belonging to a different path (the actual
781+ // input). Used by `--main-file` for projects where headers do not appear in
782+ // the compilation database.
783+ class main_file_database : public clang ::tooling::CompilationDatabase {
784+ public:
785+ main_file_database (const clang::tooling::CompilationDatabase &inner,
786+ std::string donor)
787+ : inner_(inner), donor_(std::move(donor)) {}
788+
789+ std::vector<clang::tooling::CompileCommand>
790+ getCompileCommands (llvm::StringRef path) const override {
791+ auto commands = inner_.getCompileCommands (donor_);
792+ for (auto &command : commands)
793+ rewrite (command, path);
794+ return commands;
795+ }
796+
797+ // Stub these out for safety since we do not use them.
798+ std::vector<std::string> getAllFiles () const override { return {}; }
799+
800+ std::vector<clang::tooling::CompileCommand>
801+ getAllCompileCommands () const override {
802+ return {};
803+ }
804+
805+ private:
806+ static void rewrite (clang::tooling::CompileCommand &command,
807+ llvm::StringRef path) {
808+ static constexpr std::array<llvm::StringRef, 5 > kSourceExts {
809+ " .cpp" , " .cc" , " .cxx" , " .c++" , " .C" ,
810+ };
811+
812+ // Replace the first source-file argument with `path`. If none is
813+ // present, append `path` as the input.
814+ bool swapped = false ;
815+ for (auto &arg : command.CommandLine ) {
816+ if (contains (kSourceExts , llvm::sys::path::extension (llvm::StringRef (arg)))) {
817+ arg = path.str ();
818+ swapped = true ;
819+ break ;
820+ }
821+ }
822+ if (!swapped)
823+ command.CommandLine .push_back (path.str ());
824+
825+ command.Filename = path.str ();
826+ }
827+
828+ const clang::tooling::CompilationDatabase &inner_;
829+ std::string donor_;
830+ };
740831}
741832
742833int main (int argc, char *argv[]) {
@@ -746,7 +837,13 @@ int main(int argc, char *argv[]) {
746837 CommonOptionsParser::create (argc, const_cast <const char **>(argv),
747838 idt::category, llvm::cl::OneOrMore);
748839 if (options) {
749- ClangTool tool{options->getCompilations (), options->getSourcePathList ()};
840+ std::unique_ptr<CompilationDatabase> wrapped;
841+ CompilationDatabase *cdb = &options->getCompilations ();
842+ if (!main_file.empty ()) {
843+ wrapped = std::make_unique<idt::main_file_database>(*cdb, main_file);
844+ cdb = wrapped.get ();
845+ }
846+ ClangTool tool{*cdb, options->getSourcePathList ()};
750847 return tool.run (new idt::factory{});
751848 } else {
752849 llvm::logAllUnhandledErrors (std::move (options.takeError ()), llvm::errs ());
0 commit comments