diff --git a/clang/include/clang/Tooling/Tooling.h b/clang/include/clang/Tooling/Tooling.h --- a/clang/include/clang/Tooling/Tooling.h +++ b/clang/include/clang/Tooling/Tooling.h @@ -66,6 +66,14 @@ class CompilationDatabase; +/// Retrieves the flags of the `-cc1` job in `Compilation` that has only source +/// files as its inputs. +/// Returns nullptr if there are no such jobs or multiple of them. Note that +/// offloading jobs are ignored. +const llvm::opt::ArgStringList * +getCC1Arguments(DiagnosticsEngine *Diagnostics, + driver::Compilation *Compilation); + /// Interface to process a clang::CompilerInvocation. /// /// If your tool is based on FrontendAction, you should be deriving from diff --git a/clang/lib/Tooling/Tooling.cpp b/clang/lib/Tooling/Tooling.cpp --- a/clang/lib/Tooling/Tooling.cpp +++ b/clang/lib/Tooling/Tooling.cpp @@ -83,16 +83,20 @@ return CompilerDriver; } -/// Retrieves the clang CC1 specific flags out of the compilation's jobs. -/// -/// Returns nullptr on error. -static const llvm::opt::ArgStringList *getCC1Arguments( - DiagnosticsEngine *Diagnostics, driver::Compilation *Compilation) { - // We expect to get back exactly one Command job, if we didn't something - // failed. Extract that job from the Compilation. +/// Decide whether extra compiler frontend commands can be ignored. +static bool ignoreExtraCC1Commands(const driver::Compilation *Compilation) { const driver::JobList &Jobs = Compilation->getJobs(); const driver::ActionList &Actions = Compilation->getActions(); + bool OffloadCompilation = false; + + // Jobs and Actions look very different depending on whether the Clang tool + // injected -fsyntax-only or not. Try to handle both cases here. + + for (const auto &Job : Jobs) + if (StringRef(Job.getExecutable()) == "clang-offload-bundler") + OffloadCompilation = true; + if (Jobs.size() > 1) { for (auto A : Actions){ // On MacOSX real actions may end up being wrapped in BindArchAction @@ -117,8 +121,33 @@ } } } - if (Jobs.size() == 0 || !isa(*Jobs.begin()) || - (Jobs.size() > 1 && !OffloadCompilation)) { + + return OffloadCompilation; +} + +namespace clang { +namespace tooling { + +const llvm::opt::ArgStringList * +getCC1Arguments(DiagnosticsEngine *Diagnostics, + driver::Compilation *Compilation) { + const driver::JobList &Jobs = Compilation->getJobs(); + + auto IsCC1Command = [](const driver::Command &Cmd) { + return StringRef(Cmd.getCreator().getName()) == "clang"; + }; + + auto IsSrcFile = [](const driver::InputInfo &II) { + return isSrcFile(II.getType()); + }; + + llvm::SmallVector CC1Jobs; + for (const driver::Command &Job : Jobs) + if (IsCC1Command(Job) && llvm::all_of(Job.getInputInfos(), IsSrcFile)) + CC1Jobs.push_back(&Job); + + if (CC1Jobs.empty() || + (CC1Jobs.size() > 1 && !ignoreExtraCC1Commands(Compilation))) { SmallString<256> error_msg; llvm::raw_svector_ostream error_stream(error_msg); Jobs.Print(error_stream, "; ", true); @@ -127,19 +156,9 @@ return nullptr; } - // The one job we find should be to invoke clang again. - const auto &Cmd = cast(*Jobs.begin()); - if (StringRef(Cmd.getCreator().getName()) != "clang") { - Diagnostics->Report(diag::err_fe_expected_clang_command); - return nullptr; - } - - return &Cmd.getArguments(); + return &CC1Jobs[0]->getArguments(); } -namespace clang { -namespace tooling { - /// Returns a clang build invocation initialized from the CC1 flags. CompilerInvocation *newInvocation(DiagnosticsEngine *Diagnostics, const llvm::opt::ArgStringList &CC1Args, diff --git a/clang/test/Tooling/clang-check-offload.cpp b/clang/test/Tooling/clang-check-offload.cpp deleted file mode 100644 --- a/clang/test/Tooling/clang-check-offload.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// RUN: not clang-check "%s" -- -c -x hip -nogpulib 2>&1 | FileCheck %s - -// CHECK: C++ requires -invalid; diff --git a/clang/unittests/Tooling/ToolingTest.cpp b/clang/unittests/Tooling/ToolingTest.cpp --- a/clang/unittests/Tooling/ToolingTest.cpp +++ b/clang/unittests/Tooling/ToolingTest.cpp @@ -9,6 +9,8 @@ #include "clang/AST/ASTConsumer.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclGroup.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" @@ -18,6 +20,7 @@ #include "clang/Tooling/Tooling.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Host.h" #include "llvm/Support/Path.h" #include "llvm/Support/TargetRegistry.h" #include "llvm/Support/TargetSelect.h" @@ -258,6 +261,105 @@ EXPECT_TRUE(Consumer.SawSourceManager); } +namespace { +/// Overlays the real filesystem with the given VFS and returns the result. +llvm::IntrusiveRefCntPtr +overlayRealFS(llvm::IntrusiveRefCntPtr VFS) { + auto RFS = llvm::vfs::getRealFileSystem(); + auto OverlayFS = llvm::makeIntrusiveRefCnt(RFS); + OverlayFS->pushOverlay(VFS); + return OverlayFS; +} + +struct CommandLineExtractorTest : public ::testing::Test { + llvm::IntrusiveRefCntPtr InMemoryFS; + llvm::IntrusiveRefCntPtr Diags; + driver::Driver Driver; + +public: + CommandLineExtractorTest() + : InMemoryFS(new llvm::vfs::InMemoryFileSystem), + Diags(CompilerInstance::createDiagnostics(new DiagnosticOptions)), + Driver("clang", llvm::sys::getDefaultTargetTriple(), *Diags, + "clang LLVM compiler", overlayRealFS(InMemoryFS)) {} + + void addFile(StringRef Name, StringRef Content) { + InMemoryFS->addFile(Name, 0, llvm::MemoryBuffer::getMemBuffer(Content)); + } + + const llvm::opt::ArgStringList * + extractCC1Arguments(llvm::ArrayRef Argv) { + const std::unique_ptr Compilation( + Driver.BuildCompilation(llvm::makeArrayRef(Argv))); + + return getCC1Arguments(Diags.get(), Compilation.get()); + } +}; +} // namespace + +TEST_F(CommandLineExtractorTest, AcceptOffloading) { + addFile("test.c", "int main() {}\n"); + const char *Args[] = {"clang", "-target", "arm64-apple-macosx11.0.0", + "-x", "hip", "test.c", + "-nogpulib", "-nogpuinc"}; + EXPECT_NE(extractCC1Arguments(Args), nullptr); +} + +TEST_F(CommandLineExtractorTest, AcceptOffloadingCompile) { + addFile("test.c", "int main() {}\n"); + const char *Args[] = {"clang", "-target", "arm64-apple-macosx11.0.0", + "-c", "-x", "hip", + "test.c", "-nogpulib", "-nogpuinc"}; + EXPECT_NE(extractCC1Arguments(Args), nullptr); +} + +TEST_F(CommandLineExtractorTest, AcceptOffloadingSyntaxOnly) { + addFile("test.c", "int main() {}\n"); + const char *Args[] = { + "clang", "-target", "arm64-apple-macosx11.0.0", + "-fsyntax-only", "-x", "hip", + "test.c", "-nogpulib", "-nogpuinc"}; + EXPECT_NE(extractCC1Arguments(Args), nullptr); +} + +TEST_F(CommandLineExtractorTest, AcceptExternalAssembler) { + addFile("test.c", "int main() {}\n"); + const char *Args[] = { + "clang", "-target", "arm64-apple-macosx11.0.0", "-fno-integrated-as", + "-c", "test.c"}; + EXPECT_NE(extractCC1Arguments(Args), nullptr); +} + +TEST_F(CommandLineExtractorTest, AcceptEmbedBitcode) { + addFile("test.c", "int main() {}\n"); + const char *Args[] = {"clang", "-target", "arm64-apple-macosx11.0.0", + "-c", "-fembed-bitcode", "test.c"}; + EXPECT_NE(extractCC1Arguments(Args), nullptr); +} + +TEST_F(CommandLineExtractorTest, AcceptSaveTemps) { + addFile("test.c", "int main() {}\n"); + const char *Args[] = {"clang", "-target", "arm64-apple-macosx11.0.0", + "-c", "-save-temps", "test.c"}; + EXPECT_NE(extractCC1Arguments(Args), nullptr); +} + +TEST_F(CommandLineExtractorTest, RejectMultipleArchitectures) { + addFile("test.c", "int main() {}\n"); + const char *Args[] = {"clang", "-target", "arm64-apple-macosx11.0.0", + "-arch", "x86_64", "-arch", + "arm64", "-c", "test.c"}; + EXPECT_EQ(extractCC1Arguments(Args), nullptr); +} + +TEST_F(CommandLineExtractorTest, RejectMultipleInputFiles) { + addFile("one.c", "void one() {}\n"); + addFile("two.c", "void two() {}\n"); + const char *Args[] = {"clang", "-target", "arm64-apple-macosx11.0.0", + "-c", "one.c", "two.c"}; + EXPECT_EQ(extractCC1Arguments(Args), nullptr); +} + struct VerifyEndCallback : public SourceFileCallbacks { VerifyEndCallback() : BeginCalled(0), EndCalled(0), Matched(false) {} bool handleBeginSource(CompilerInstance &CI) override {