Index: clang/include/clang/Tooling/Tooling.h =================================================================== --- clang/include/clang/Tooling/Tooling.h +++ clang/include/clang/Tooling/Tooling.h @@ -66,6 +66,11 @@ class CompilationDatabase; +/// Retrieves the clang CC1 specific flags out of the compilation's jobs. +/// Returns nullptr on error. +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 Index: clang/lib/Tooling/Tooling.cpp =================================================================== --- clang/lib/Tooling/Tooling.cpp +++ clang/lib/Tooling/Tooling.cpp @@ -83,16 +83,25 @@ 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. +/// Determine whether the given command is invoking the compiler frontend. +static bool isCC1Command(const driver::Command &Cmd) { + return StringRef(Cmd.getCreator().getName()) == "clang"; +} + +/// 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 +126,24 @@ } } } - 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(); + + llvm::SmallVector CC1Jobs; + for (const driver::Command &Job : Jobs) + if (isCC1Command(Job)) + 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 +152,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, Index: clang/test/Tooling/clang-check-offload.cpp =================================================================== --- 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; Index: clang/unittests/Tooling/ToolingTest.cpp =================================================================== --- clang/unittests/Tooling/ToolingTest.cpp +++ 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,84 @@ 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", "-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", "-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", "-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", "-fno-integrated-as", "-c", "test.c"}; + EXPECT_NE(extractCC1Arguments(Args), nullptr); +} + +TEST_F(CommandLineExtractorTest, RejectMultipleArchitectures) { + addFile("test.c", "int main() {}\n"); + const char *Args[] = {"clang", "-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", "-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 {