Dmitry Polukhin d60d3455eb [clangd] Move standard options adaptor to CommandMangler
There is a discrepancy between how clangd processes CDB loaded from
JSON file on disk and pushed via LSP. Thus the same CDB pushed via
LSP protocol may not work as expected. Some difference between these two
paths is expected but we still need to insert driver mode and target from
binary name and expand response files.

Test Plan: check-clang-tools

Differential Revision: https://reviews.llvm.org/D143436
2023-03-17 03:10:36 -07:00

1004 lines
37 KiB
C++

//===- unittest/Tooling/ToolingTest.cpp - Tooling unit tests --------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "clang/Tooling/Tooling.h"
#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"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/TextDiagnosticBuffer.h"
#include "clang/Testing/CommandLineArgs.h"
#include "clang/Tooling/ArgumentsAdjusters.h"
#include "clang/Tooling/CompilationDatabase.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/TargetParser/Host.h"
#include "gtest/gtest.h"
#include <algorithm>
#include <string>
#include <vector>
namespace clang {
namespace tooling {
namespace {
/// Takes an ast consumer and returns it from CreateASTConsumer. This only
/// works with single translation unit compilations.
class TestAction : public clang::ASTFrontendAction {
public:
/// Takes ownership of TestConsumer.
explicit TestAction(std::unique_ptr<clang::ASTConsumer> TestConsumer)
: TestConsumer(std::move(TestConsumer)) {}
protected:
std::unique_ptr<clang::ASTConsumer>
CreateASTConsumer(clang::CompilerInstance &compiler,
StringRef dummy) override {
/// TestConsumer will be deleted by the framework calling us.
return std::move(TestConsumer);
}
private:
std::unique_ptr<clang::ASTConsumer> TestConsumer;
};
class FindTopLevelDeclConsumer : public clang::ASTConsumer {
public:
explicit FindTopLevelDeclConsumer(bool *FoundTopLevelDecl)
: FoundTopLevelDecl(FoundTopLevelDecl) {}
bool HandleTopLevelDecl(clang::DeclGroupRef DeclGroup) override {
*FoundTopLevelDecl = true;
return true;
}
private:
bool * const FoundTopLevelDecl;
};
} // end namespace
TEST(runToolOnCode, FindsNoTopLevelDeclOnEmptyCode) {
bool FoundTopLevelDecl = false;
EXPECT_TRUE(runToolOnCode(
std::make_unique<TestAction>(
std::make_unique<FindTopLevelDeclConsumer>(&FoundTopLevelDecl)),
""));
EXPECT_FALSE(FoundTopLevelDecl);
}
namespace {
class FindClassDeclXConsumer : public clang::ASTConsumer {
public:
FindClassDeclXConsumer(bool *FoundClassDeclX)
: FoundClassDeclX(FoundClassDeclX) {}
bool HandleTopLevelDecl(clang::DeclGroupRef GroupRef) override {
if (CXXRecordDecl* Record = dyn_cast<clang::CXXRecordDecl>(
*GroupRef.begin())) {
if (Record->getName() == "X") {
*FoundClassDeclX = true;
}
}
return true;
}
private:
bool *FoundClassDeclX;
};
bool FindClassDeclX(ASTUnit *AST) {
for (std::vector<Decl *>::iterator i = AST->top_level_begin(),
e = AST->top_level_end();
i != e; ++i) {
if (CXXRecordDecl* Record = dyn_cast<clang::CXXRecordDecl>(*i)) {
if (Record->getName() == "X") {
return true;
}
}
}
return false;
}
struct TestDiagnosticConsumer : public DiagnosticConsumer {
TestDiagnosticConsumer() : NumDiagnosticsSeen(0) {}
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
const Diagnostic &Info) override {
++NumDiagnosticsSeen;
}
unsigned NumDiagnosticsSeen;
};
} // end namespace
TEST(runToolOnCode, FindsClassDecl) {
bool FoundClassDeclX = false;
EXPECT_TRUE(runToolOnCode(
std::make_unique<TestAction>(
std::make_unique<FindClassDeclXConsumer>(&FoundClassDeclX)),
"class X;"));
EXPECT_TRUE(FoundClassDeclX);
FoundClassDeclX = false;
EXPECT_TRUE(runToolOnCode(
std::make_unique<TestAction>(
std::make_unique<FindClassDeclXConsumer>(&FoundClassDeclX)),
"class Y;"));
EXPECT_FALSE(FoundClassDeclX);
}
TEST(buildASTFromCode, FindsClassDecl) {
std::unique_ptr<ASTUnit> AST = buildASTFromCode("class X;");
ASSERT_TRUE(AST.get());
EXPECT_TRUE(FindClassDeclX(AST.get()));
AST = buildASTFromCode("class Y;");
ASSERT_TRUE(AST.get());
EXPECT_FALSE(FindClassDeclX(AST.get()));
}
TEST(buildASTFromCode, ReportsErrors) {
TestDiagnosticConsumer Consumer;
std::unique_ptr<ASTUnit> AST = buildASTFromCodeWithArgs(
"int x = \"A\";", {}, "input.cc", "clang-tool",
std::make_shared<PCHContainerOperations>(),
getClangStripDependencyFileAdjuster(), FileContentMappings(), &Consumer);
EXPECT_TRUE(AST.get());
EXPECT_EQ(1u, Consumer.NumDiagnosticsSeen);
}
TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromType) {
std::unique_ptr<FrontendActionFactory> Factory(
newFrontendActionFactory<SyntaxOnlyAction>());
std::unique_ptr<FrontendAction> Action(Factory->create());
EXPECT_TRUE(Action.get() != nullptr);
}
struct IndependentFrontendActionCreator {
std::unique_ptr<ASTConsumer> newASTConsumer() {
return std::make_unique<FindTopLevelDeclConsumer>(nullptr);
}
};
TEST(newFrontendActionFactory, CreatesFrontendActionFactoryFromFactoryType) {
IndependentFrontendActionCreator Creator;
std::unique_ptr<FrontendActionFactory> Factory(
newFrontendActionFactory(&Creator));
std::unique_ptr<FrontendAction> Action(Factory->create());
EXPECT_TRUE(Action.get() != nullptr);
}
TEST(ToolInvocation, TestMapVirtualFile) {
llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem(
new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem()));
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
new llvm::vfs::InMemoryFileSystem);
OverlayFileSystem->pushOverlay(InMemoryFileSystem);
llvm::IntrusiveRefCntPtr<FileManager> Files(
new FileManager(FileSystemOptions(), OverlayFileSystem));
std::vector<std::string> Args;
Args.push_back("tool-executable");
Args.push_back("-Idef");
Args.push_back("-fsyntax-only");
Args.push_back("test.cpp");
clang::tooling::ToolInvocation Invocation(
Args, std::make_unique<SyntaxOnlyAction>(), Files.get());
InMemoryFileSystem->addFile(
"test.cpp", 0, llvm::MemoryBuffer::getMemBuffer("#include <abc>\n"));
InMemoryFileSystem->addFile("def/abc", 0,
llvm::MemoryBuffer::getMemBuffer("\n"));
EXPECT_TRUE(Invocation.run());
}
TEST(ToolInvocation, TestVirtualModulesCompilation) {
// FIXME: Currently, this only tests that we don't exit with an error if a
// mapped module.map is found on the include path. In the future, expand this
// test to run a full modules enabled compilation, so we make sure we can
// rerun modules compilations with a virtual file system.
llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem(
new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem()));
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
new llvm::vfs::InMemoryFileSystem);
OverlayFileSystem->pushOverlay(InMemoryFileSystem);
llvm::IntrusiveRefCntPtr<FileManager> Files(
new FileManager(FileSystemOptions(), OverlayFileSystem));
std::vector<std::string> Args;
Args.push_back("tool-executable");
Args.push_back("-Idef");
Args.push_back("-fsyntax-only");
Args.push_back("test.cpp");
clang::tooling::ToolInvocation Invocation(
Args, std::make_unique<SyntaxOnlyAction>(), Files.get());
InMemoryFileSystem->addFile(
"test.cpp", 0, llvm::MemoryBuffer::getMemBuffer("#include <abc>\n"));
InMemoryFileSystem->addFile("def/abc", 0,
llvm::MemoryBuffer::getMemBuffer("\n"));
// Add a module.map file in the include directory of our header, so we trigger
// the module.map header search logic.
InMemoryFileSystem->addFile("def/module.map", 0,
llvm::MemoryBuffer::getMemBuffer("\n"));
EXPECT_TRUE(Invocation.run());
}
TEST(ToolInvocation, DiagnosticsEngineProperlyInitializedForCC1Construction) {
llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem(
new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem()));
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
new llvm::vfs::InMemoryFileSystem);
OverlayFileSystem->pushOverlay(InMemoryFileSystem);
llvm::IntrusiveRefCntPtr<FileManager> Files(
new FileManager(FileSystemOptions(), OverlayFileSystem));
std::vector<std::string> Args;
Args.push_back("tool-executable");
// Unknown warning option will result in a warning.
Args.push_back("-fexpensive-optimizations");
// Argument that will suppress the warning above.
Args.push_back("-Wno-ignored-optimization-argument");
Args.push_back("-E");
Args.push_back("test.cpp");
clang::tooling::ToolInvocation Invocation(
Args, std::make_unique<SyntaxOnlyAction>(), Files.get());
InMemoryFileSystem->addFile("test.cpp", 0,
llvm::MemoryBuffer::getMemBuffer(""));
TextDiagnosticBuffer Consumer;
Invocation.setDiagnosticConsumer(&Consumer);
EXPECT_TRUE(Invocation.run());
// Check that the warning was ignored due to the '-Wno-xxx' argument.
EXPECT_EQ(std::distance(Consumer.warn_begin(), Consumer.warn_end()), 0u);
}
TEST(ToolInvocation, CustomDiagnosticOptionsOverwriteParsedOnes) {
llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem(
new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem()));
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
new llvm::vfs::InMemoryFileSystem);
OverlayFileSystem->pushOverlay(InMemoryFileSystem);
llvm::IntrusiveRefCntPtr<FileManager> Files(
new FileManager(FileSystemOptions(), OverlayFileSystem));
std::vector<std::string> Args;
Args.push_back("tool-executable");
// Unknown warning option will result in a warning.
Args.push_back("-fexpensive-optimizations");
// Argument that will suppress the warning above.
Args.push_back("-Wno-ignored-optimization-argument");
Args.push_back("-E");
Args.push_back("test.cpp");
clang::tooling::ToolInvocation Invocation(
Args, std::make_unique<SyntaxOnlyAction>(), Files.get());
InMemoryFileSystem->addFile("test.cpp", 0,
llvm::MemoryBuffer::getMemBuffer(""));
TextDiagnosticBuffer Consumer;
Invocation.setDiagnosticConsumer(&Consumer);
// Inject custom `DiagnosticOptions` for command-line parsing.
auto DiagOpts = llvm::makeIntrusiveRefCnt<DiagnosticOptions>();
Invocation.setDiagnosticOptions(&*DiagOpts);
EXPECT_TRUE(Invocation.run());
// Check that the warning was issued during command-line parsing due to the
// custom `DiagnosticOptions` without '-Wno-xxx'.
EXPECT_EQ(std::distance(Consumer.warn_begin(), Consumer.warn_end()), 1u);
}
struct DiagnosticConsumerExpectingSourceManager : public DiagnosticConsumer {
bool SawSourceManager;
DiagnosticConsumerExpectingSourceManager() : SawSourceManager(false) {}
void HandleDiagnostic(clang::DiagnosticsEngine::Level,
const clang::Diagnostic &info) override {
SawSourceManager = info.hasSourceManager();
}
};
TEST(ToolInvocation, DiagConsumerExpectingSourceManager) {
llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem(
new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem()));
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
new llvm::vfs::InMemoryFileSystem);
OverlayFileSystem->pushOverlay(InMemoryFileSystem);
llvm::IntrusiveRefCntPtr<FileManager> Files(
new FileManager(FileSystemOptions(), OverlayFileSystem));
std::vector<std::string> Args;
Args.push_back("tool-executable");
// Note: intentional error; user probably meant -ferror-limit=0.
Args.push_back("-ferror-limit=-1");
Args.push_back("-fsyntax-only");
Args.push_back("test.cpp");
clang::tooling::ToolInvocation Invocation(
Args, std::make_unique<SyntaxOnlyAction>(), Files.get());
InMemoryFileSystem->addFile(
"test.cpp", 0, llvm::MemoryBuffer::getMemBuffer("int main() {}\n"));
DiagnosticConsumerExpectingSourceManager Consumer;
Invocation.setDiagnosticConsumer(&Consumer);
EXPECT_TRUE(Invocation.run());
EXPECT_TRUE(Consumer.SawSourceManager);
}
TEST(ToolInvocation, CC1Args) {
llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem(
new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem()));
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
new llvm::vfs::InMemoryFileSystem);
OverlayFileSystem->pushOverlay(InMemoryFileSystem);
llvm::IntrusiveRefCntPtr<FileManager> Files(
new FileManager(FileSystemOptions(), OverlayFileSystem));
std::vector<std::string> Args;
Args.push_back("tool-executable");
Args.push_back("-cc1");
Args.push_back("-fsyntax-only");
Args.push_back("test.cpp");
clang::tooling::ToolInvocation Invocation(
Args, std::make_unique<SyntaxOnlyAction>(), Files.get());
InMemoryFileSystem->addFile(
"test.cpp", 0, llvm::MemoryBuffer::getMemBuffer("void foo(void);\n"));
EXPECT_TRUE(Invocation.run());
}
TEST(ToolInvocation, CC1ArgsInvalid) {
llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem(
new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem()));
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
new llvm::vfs::InMemoryFileSystem);
OverlayFileSystem->pushOverlay(InMemoryFileSystem);
llvm::IntrusiveRefCntPtr<FileManager> Files(
new FileManager(FileSystemOptions(), OverlayFileSystem));
std::vector<std::string> Args;
Args.push_back("tool-executable");
Args.push_back("-cc1");
Args.push_back("-invalid-arg");
Args.push_back("test.cpp");
clang::tooling::ToolInvocation Invocation(
Args, std::make_unique<SyntaxOnlyAction>(), Files.get());
InMemoryFileSystem->addFile(
"test.cpp", 0, llvm::MemoryBuffer::getMemBuffer("void foo(void);\n"));
EXPECT_FALSE(Invocation.run());
}
namespace {
/// Overlays the real filesystem with the given VFS and returns the result.
llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem>
overlayRealFS(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) {
auto RFS = llvm::vfs::getRealFileSystem();
auto OverlayFS = llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(RFS);
OverlayFS->pushOverlay(VFS);
return OverlayFS;
}
struct CommandLineExtractorTest : public ::testing::Test {
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFS;
llvm::IntrusiveRefCntPtr<DiagnosticsEngine> 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<const char *> Argv) {
const std::unique_ptr<driver::Compilation> Compilation(
Driver.BuildCompilation(llvm::ArrayRef(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 {
++BeginCalled;
return true;
}
void handleEndSource() override { ++EndCalled; }
std::unique_ptr<ASTConsumer> newASTConsumer() {
return std::make_unique<FindTopLevelDeclConsumer>(&Matched);
}
unsigned BeginCalled;
unsigned EndCalled;
bool Matched;
};
#if !defined(_WIN32)
TEST(newFrontendActionFactory, InjectsSourceFileCallbacks) {
VerifyEndCallback EndCallback;
FixedCompilationDatabase Compilations("/", std::vector<std::string>());
std::vector<std::string> Sources;
Sources.push_back("/a.cc");
Sources.push_back("/b.cc");
ClangTool Tool(Compilations, Sources);
Tool.mapVirtualFile("/a.cc", "void a() {}");
Tool.mapVirtualFile("/b.cc", "void b() {}");
std::unique_ptr<FrontendActionFactory> Action(
newFrontendActionFactory(&EndCallback, &EndCallback));
Tool.run(Action.get());
EXPECT_TRUE(EndCallback.Matched);
EXPECT_EQ(2u, EndCallback.BeginCalled);
EXPECT_EQ(2u, EndCallback.EndCalled);
}
#endif
struct SkipBodyConsumer : public clang::ASTConsumer {
/// Skip the 'skipMe' function.
bool shouldSkipFunctionBody(Decl *D) override {
NamedDecl *F = dyn_cast<NamedDecl>(D);
return F && F->getNameAsString() == "skipMe";
}
};
struct SkipBodyAction : public clang::ASTFrontendAction {
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
StringRef) override {
Compiler.getFrontendOpts().SkipFunctionBodies = true;
return std::make_unique<SkipBodyConsumer>();
}
};
TEST(runToolOnCode, TestSkipFunctionBody) {
std::vector<std::string> Args = {"-std=c++11"};
std::vector<std::string> Args2 = {"-fno-delayed-template-parsing"};
EXPECT_TRUE(runToolOnCode(std::make_unique<SkipBodyAction>(),
"int skipMe() { an_error_here }"));
EXPECT_FALSE(runToolOnCode(std::make_unique<SkipBodyAction>(),
"int skipMeNot() { an_error_here }"));
// Test constructors with initializers
EXPECT_TRUE(runToolOnCodeWithArgs(
std::make_unique<SkipBodyAction>(),
"struct skipMe { skipMe() : an_error() { more error } };", Args));
EXPECT_TRUE(runToolOnCodeWithArgs(
std::make_unique<SkipBodyAction>(), "struct skipMe { skipMe(); };"
"skipMe::skipMe() : an_error([](){;}) { more error }",
Args));
EXPECT_TRUE(runToolOnCodeWithArgs(
std::make_unique<SkipBodyAction>(), "struct skipMe { skipMe(); };"
"skipMe::skipMe() : an_error{[](){;}} { more error }",
Args));
EXPECT_TRUE(runToolOnCodeWithArgs(
std::make_unique<SkipBodyAction>(),
"struct skipMe { skipMe(); };"
"skipMe::skipMe() : a<b<c>(e)>>(), f{}, g() { error }",
Args));
EXPECT_TRUE(runToolOnCodeWithArgs(
std::make_unique<SkipBodyAction>(), "struct skipMe { skipMe() : bases()... { error } };",
Args));
EXPECT_FALSE(runToolOnCodeWithArgs(
std::make_unique<SkipBodyAction>(), "struct skipMeNot { skipMeNot() : an_error() { } };",
Args));
EXPECT_FALSE(runToolOnCodeWithArgs(std::make_unique<SkipBodyAction>(),
"struct skipMeNot { skipMeNot(); };"
"skipMeNot::skipMeNot() : an_error() { }",
Args));
// Try/catch
EXPECT_TRUE(runToolOnCode(
std::make_unique<SkipBodyAction>(),
"void skipMe() try { an_error() } catch(error) { error };"));
EXPECT_TRUE(runToolOnCode(
std::make_unique<SkipBodyAction>(),
"struct S { void skipMe() try { an_error() } catch(error) { error } };"));
EXPECT_TRUE(
runToolOnCode(std::make_unique<SkipBodyAction>(),
"void skipMe() try { an_error() } catch(error) { error; }"
"catch(error) { error } catch (error) { }"));
EXPECT_FALSE(runToolOnCode(
std::make_unique<SkipBodyAction>(),
"void skipMe() try something;")); // don't crash while parsing
// Template
EXPECT_TRUE(runToolOnCode(
std::make_unique<SkipBodyAction>(), "template<typename T> int skipMe() { an_error_here }"
"int x = skipMe<int>();"));
EXPECT_FALSE(runToolOnCodeWithArgs(
std::make_unique<SkipBodyAction>(),
"template<typename T> int skipMeNot() { an_error_here }", Args2));
}
TEST(runToolOnCodeWithArgs, TestNoDepFile) {
llvm::SmallString<32> DepFilePath;
ASSERT_FALSE(llvm::sys::fs::getPotentiallyUniqueTempFileName("depfile", "d",
DepFilePath));
std::vector<std::string> Args;
Args.push_back("-MMD");
Args.push_back("-MT");
Args.push_back(std::string(DepFilePath.str()));
Args.push_back("-MF");
Args.push_back(std::string(DepFilePath.str()));
EXPECT_TRUE(runToolOnCodeWithArgs(std::make_unique<SkipBodyAction>(), "", Args));
EXPECT_FALSE(llvm::sys::fs::exists(DepFilePath.str()));
EXPECT_FALSE(llvm::sys::fs::remove(DepFilePath.str()));
}
struct CheckColoredDiagnosticsAction : public clang::ASTFrontendAction {
CheckColoredDiagnosticsAction(bool ShouldShowColor)
: ShouldShowColor(ShouldShowColor) {}
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
StringRef) override {
if (Compiler.getDiagnosticOpts().ShowColors != ShouldShowColor)
Compiler.getDiagnostics().Report(
Compiler.getDiagnostics().getCustomDiagID(
DiagnosticsEngine::Fatal,
"getDiagnosticOpts().ShowColors != ShouldShowColor"));
return std::make_unique<ASTConsumer>();
}
private:
bool ShouldShowColor = true;
};
TEST(runToolOnCodeWithArgs, DiagnosticsColor) {
EXPECT_TRUE(runToolOnCodeWithArgs(
std::make_unique<CheckColoredDiagnosticsAction>(true), "",
{"-fcolor-diagnostics"}));
EXPECT_TRUE(runToolOnCodeWithArgs(
std::make_unique<CheckColoredDiagnosticsAction>(false), "",
{"-fno-color-diagnostics"}));
EXPECT_TRUE(runToolOnCodeWithArgs(
std::make_unique<CheckColoredDiagnosticsAction>(true), "",
{"-fno-color-diagnostics", "-fcolor-diagnostics"}));
EXPECT_TRUE(runToolOnCodeWithArgs(
std::make_unique<CheckColoredDiagnosticsAction>(false), "",
{"-fcolor-diagnostics", "-fno-color-diagnostics"}));
EXPECT_TRUE(runToolOnCodeWithArgs(
std::make_unique<CheckColoredDiagnosticsAction>(true), "",
{"-fno-color-diagnostics", "-fdiagnostics-color=always"}));
// Check that this test would fail if ShowColors is not what it should.
EXPECT_FALSE(runToolOnCodeWithArgs(
std::make_unique<CheckColoredDiagnosticsAction>(false), "",
{"-fcolor-diagnostics"}));
}
TEST(ClangToolTest, ArgumentAdjusters) {
FixedCompilationDatabase Compilations("/", std::vector<std::string>());
ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc"));
Tool.mapVirtualFile("/a.cc", "void a() {}");
std::unique_ptr<FrontendActionFactory> Action(
newFrontendActionFactory<SyntaxOnlyAction>());
bool Found = false;
bool Ran = false;
ArgumentsAdjuster CheckSyntaxOnlyAdjuster =
[&Found, &Ran](const CommandLineArguments &Args, StringRef /*unused*/) {
Ran = true;
if (llvm::is_contained(Args, "-fsyntax-only"))
Found = true;
return Args;
};
Tool.appendArgumentsAdjuster(CheckSyntaxOnlyAdjuster);
Tool.run(Action.get());
EXPECT_TRUE(Ran);
EXPECT_TRUE(Found);
Ran = Found = false;
Tool.clearArgumentsAdjusters();
Tool.appendArgumentsAdjuster(CheckSyntaxOnlyAdjuster);
Tool.appendArgumentsAdjuster(getClangSyntaxOnlyAdjuster());
Tool.run(Action.get());
EXPECT_TRUE(Ran);
EXPECT_FALSE(Found);
}
TEST(ClangToolTest, NoDoubleSyntaxOnly) {
FixedCompilationDatabase Compilations("/", {"-fsyntax-only"});
ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc"));
Tool.mapVirtualFile("/a.cc", "void a() {}");
std::unique_ptr<FrontendActionFactory> Action(
newFrontendActionFactory<SyntaxOnlyAction>());
size_t SyntaxOnlyCount = 0;
ArgumentsAdjuster CheckSyntaxOnlyAdjuster =
[&SyntaxOnlyCount](const CommandLineArguments &Args,
StringRef /*unused*/) {
for (llvm::StringRef Arg : Args) {
if (Arg == "-fsyntax-only")
++SyntaxOnlyCount;
}
return Args;
};
Tool.clearArgumentsAdjusters();
Tool.appendArgumentsAdjuster(getClangSyntaxOnlyAdjuster());
Tool.appendArgumentsAdjuster(CheckSyntaxOnlyAdjuster);
Tool.run(Action.get());
EXPECT_EQ(SyntaxOnlyCount, 1U);
}
TEST(ClangToolTest, NoOutputCommands) {
FixedCompilationDatabase Compilations("/", {"-save-temps", "-save-temps=cwd",
"--save-temps",
"--save-temps=somedir"});
ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc"));
Tool.mapVirtualFile("/a.cc", "void a() {}");
std::unique_ptr<FrontendActionFactory> Action(
newFrontendActionFactory<SyntaxOnlyAction>());
const std::vector<llvm::StringRef> OutputCommands = {"-save-temps"};
bool Ran = false;
ArgumentsAdjuster CheckSyntaxOnlyAdjuster =
[&OutputCommands, &Ran](const CommandLineArguments &Args,
StringRef /*unused*/) {
for (llvm::StringRef Arg : Args) {
for (llvm::StringRef OutputCommand : OutputCommands)
EXPECT_FALSE(Arg.contains(OutputCommand));
}
Ran = true;
return Args;
};
Tool.clearArgumentsAdjusters();
Tool.appendArgumentsAdjuster(getClangSyntaxOnlyAdjuster());
Tool.appendArgumentsAdjuster(CheckSyntaxOnlyAdjuster);
Tool.run(Action.get());
EXPECT_TRUE(Ran);
}
TEST(ClangToolTest, BaseVirtualFileSystemUsage) {
FixedCompilationDatabase Compilations("/", std::vector<std::string>());
llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem(
new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem()));
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem(
new llvm::vfs::InMemoryFileSystem);
OverlayFileSystem->pushOverlay(InMemoryFileSystem);
InMemoryFileSystem->addFile(
"a.cpp", 0, llvm::MemoryBuffer::getMemBuffer("int main() {}"));
ClangTool Tool(Compilations, std::vector<std::string>(1, "a.cpp"),
std::make_shared<PCHContainerOperations>(), OverlayFileSystem);
std::unique_ptr<FrontendActionFactory> Action(
newFrontendActionFactory<SyntaxOnlyAction>());
EXPECT_EQ(0, Tool.run(Action.get()));
}
// Check getClangStripDependencyFileAdjuster doesn't strip args after -MD/-MMD.
TEST(ClangToolTest, StripDependencyFileAdjuster) {
FixedCompilationDatabase Compilations("/", {"-MD", "-c", "-MMD", "-w"});
ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc"));
Tool.mapVirtualFile("/a.cc", "void a() {}");
std::unique_ptr<FrontendActionFactory> Action(
newFrontendActionFactory<SyntaxOnlyAction>());
CommandLineArguments FinalArgs;
ArgumentsAdjuster CheckFlagsAdjuster =
[&FinalArgs](const CommandLineArguments &Args, StringRef /*unused*/) {
FinalArgs = Args;
return Args;
};
Tool.clearArgumentsAdjusters();
Tool.appendArgumentsAdjuster(getClangStripDependencyFileAdjuster());
Tool.appendArgumentsAdjuster(CheckFlagsAdjuster);
Tool.run(Action.get());
auto HasFlag = [&FinalArgs](const std::string &Flag) {
return llvm::find(FinalArgs, Flag) != FinalArgs.end();
};
EXPECT_FALSE(HasFlag("-MD"));
EXPECT_FALSE(HasFlag("-MMD"));
EXPECT_TRUE(HasFlag("-c"));
EXPECT_TRUE(HasFlag("-w"));
}
// Check getClangStripDependencyFileAdjuster strips /showIncludes and variants
TEST(ClangToolTest, StripDependencyFileAdjusterShowIncludes) {
FixedCompilationDatabase Compilations(
"/", {"/showIncludes", "/showIncludes:user", "-showIncludes",
"-showIncludes:user", "-c"});
ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc"));
Tool.mapVirtualFile("/a.cc", "void a() {}");
std::unique_ptr<FrontendActionFactory> Action(
newFrontendActionFactory<SyntaxOnlyAction>());
CommandLineArguments FinalArgs;
ArgumentsAdjuster CheckFlagsAdjuster =
[&FinalArgs](const CommandLineArguments &Args, StringRef /*unused*/) {
FinalArgs = Args;
return Args;
};
Tool.clearArgumentsAdjusters();
Tool.appendArgumentsAdjuster(getClangStripDependencyFileAdjuster());
Tool.appendArgumentsAdjuster(CheckFlagsAdjuster);
Tool.run(Action.get());
auto HasFlag = [&FinalArgs](const std::string &Flag) {
return llvm::find(FinalArgs, Flag) != FinalArgs.end();
};
EXPECT_FALSE(HasFlag("/showIncludes"));
EXPECT_FALSE(HasFlag("/showIncludes:user"));
EXPECT_FALSE(HasFlag("-showIncludes"));
EXPECT_FALSE(HasFlag("-showIncludes:user"));
EXPECT_TRUE(HasFlag("-c"));
}
// Check getClangStripDependencyFileAdjuster doesn't strip args when using the
// MSVC cl.exe driver
TEST(ClangToolTest, StripDependencyFileAdjusterMsvc) {
FixedCompilationDatabase Compilations(
"/", {"--driver-mode=cl", "-MD", "-MDd", "-MT", "-O1", "-MTd", "-MP"});
ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc"));
Tool.mapVirtualFile("/a.cc", "void a() {}");
std::unique_ptr<FrontendActionFactory> Action(
newFrontendActionFactory<SyntaxOnlyAction>());
CommandLineArguments FinalArgs;
ArgumentsAdjuster CheckFlagsAdjuster =
[&FinalArgs](const CommandLineArguments &Args, StringRef /*unused*/) {
FinalArgs = Args;
return Args;
};
Tool.clearArgumentsAdjusters();
Tool.appendArgumentsAdjuster(getClangStripDependencyFileAdjuster());
Tool.appendArgumentsAdjuster(CheckFlagsAdjuster);
Tool.run(Action.get());
auto HasFlag = [&FinalArgs](const std::string &Flag) {
return llvm::find(FinalArgs, Flag) != FinalArgs.end();
};
EXPECT_TRUE(HasFlag("-MD"));
EXPECT_TRUE(HasFlag("-MDd"));
EXPECT_TRUE(HasFlag("-MT"));
EXPECT_TRUE(HasFlag("-O1"));
EXPECT_TRUE(HasFlag("-MTd"));
EXPECT_TRUE(HasFlag("-MP"));
}
// Check getClangStripPluginsAdjuster strips plugin related args.
TEST(ClangToolTest, StripPluginsAdjuster) {
FixedCompilationDatabase Compilations(
"/", {"-Xclang", "-add-plugin", "-Xclang", "random-plugin"});
ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc"));
Tool.mapVirtualFile("/a.cc", "void a() {}");
std::unique_ptr<FrontendActionFactory> Action(
newFrontendActionFactory<SyntaxOnlyAction>());
CommandLineArguments FinalArgs;
ArgumentsAdjuster CheckFlagsAdjuster =
[&FinalArgs](const CommandLineArguments &Args, StringRef /*unused*/) {
FinalArgs = Args;
return Args;
};
Tool.clearArgumentsAdjusters();
Tool.appendArgumentsAdjuster(getStripPluginsAdjuster());
Tool.appendArgumentsAdjuster(CheckFlagsAdjuster);
Tool.run(Action.get());
auto HasFlag = [&FinalArgs](const std::string &Flag) {
return llvm::find(FinalArgs, Flag) != FinalArgs.end();
};
EXPECT_FALSE(HasFlag("-Xclang"));
EXPECT_FALSE(HasFlag("-add-plugin"));
EXPECT_FALSE(HasFlag("-random-plugin"));
}
TEST(addTargetAndModeForProgramName, AddsTargetAndMode) {
llvm::InitializeAllTargets();
std::string Target = getAnyTargetForTesting();
ASSERT_FALSE(Target.empty());
std::vector<std::string> Args = {"clang", "-foo"};
addTargetAndModeForProgramName(Args, "");
EXPECT_EQ((std::vector<std::string>{"clang", "-foo"}), Args);
addTargetAndModeForProgramName(Args, Target + "-g++");
EXPECT_EQ((std::vector<std::string>{"clang", "--target=" + Target,
"--driver-mode=g++", "-foo"}),
Args);
}
TEST(addTargetAndModeForProgramName, PathIgnored) {
llvm::InitializeAllTargets();
std::string Target = getAnyTargetForTesting();
ASSERT_FALSE(Target.empty());
SmallString<32> ToolPath;
llvm::sys::path::append(ToolPath, "foo", "bar", Target + "-g++");
std::vector<std::string> Args = {"clang", "-foo"};
addTargetAndModeForProgramName(Args, ToolPath);
EXPECT_EQ((std::vector<std::string>{"clang", "--target=" + Target,
"--driver-mode=g++", "-foo"}),
Args);
}
TEST(addTargetAndModeForProgramName, IgnoresExistingTarget) {
llvm::InitializeAllTargets();
std::string Target = getAnyTargetForTesting();
ASSERT_FALSE(Target.empty());
std::vector<std::string> Args = {"clang", "-foo", "-target", "something"};
addTargetAndModeForProgramName(Args, Target + "-g++");
EXPECT_EQ((std::vector<std::string>{"clang", "--driver-mode=g++", "-foo",
"-target", "something"}),
Args);
std::vector<std::string> ArgsAlt = {"clang", "-foo", "--target=something"};
addTargetAndModeForProgramName(ArgsAlt, Target + "-g++");
EXPECT_EQ((std::vector<std::string>{"clang", "--driver-mode=g++", "-foo",
"--target=something"}),
ArgsAlt);
}
TEST(addTargetAndModeForProgramName, IgnoresExistingMode) {
llvm::InitializeAllTargets();
std::string Target = getAnyTargetForTesting();
ASSERT_FALSE(Target.empty());
std::vector<std::string> Args = {"clang", "-foo", "--driver-mode=abc"};
addTargetAndModeForProgramName(Args, Target + "-g++");
EXPECT_EQ((std::vector<std::string>{"clang", "--target=" + Target, "-foo",
"--driver-mode=abc"}),
Args);
}
#ifndef _WIN32
TEST(ClangToolTest, BuildASTs) {
FixedCompilationDatabase Compilations("/", std::vector<std::string>());
std::vector<std::string> Sources;
Sources.push_back("/a.cc");
Sources.push_back("/b.cc");
ClangTool Tool(Compilations, Sources);
Tool.mapVirtualFile("/a.cc", "void a() {}");
Tool.mapVirtualFile("/b.cc", "void b() {}");
std::vector<std::unique_ptr<ASTUnit>> ASTs;
EXPECT_EQ(0, Tool.buildASTs(ASTs));
EXPECT_EQ(2u, ASTs.size());
}
TEST(ClangToolTest, InjectDiagnosticConsumer) {
FixedCompilationDatabase Compilations("/", std::vector<std::string>());
ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc"));
Tool.mapVirtualFile("/a.cc", "int x = undeclared;");
TestDiagnosticConsumer Consumer;
Tool.setDiagnosticConsumer(&Consumer);
std::unique_ptr<FrontendActionFactory> Action(
newFrontendActionFactory<SyntaxOnlyAction>());
Tool.run(Action.get());
EXPECT_EQ(1u, Consumer.NumDiagnosticsSeen);
}
TEST(ClangToolTest, InjectDiagnosticConsumerInBuildASTs) {
FixedCompilationDatabase Compilations("/", std::vector<std::string>());
ClangTool Tool(Compilations, std::vector<std::string>(1, "/a.cc"));
Tool.mapVirtualFile("/a.cc", "int x = undeclared;");
TestDiagnosticConsumer Consumer;
Tool.setDiagnosticConsumer(&Consumer);
std::vector<std::unique_ptr<ASTUnit>> ASTs;
Tool.buildASTs(ASTs);
EXPECT_EQ(1u, ASTs.size());
EXPECT_EQ(1u, Consumer.NumDiagnosticsSeen);
}
#endif
TEST(runToolOnCode, TestResetDiagnostics) {
// This is a tool that resets the diagnostic during the compilation.
struct ResetDiagnosticAction : public clang::ASTFrontendAction {
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
StringRef) override {
struct Consumer : public clang::ASTConsumer {
bool HandleTopLevelDecl(clang::DeclGroupRef D) override {
auto &Diags = (*D.begin())->getASTContext().getDiagnostics();
// Ignore any error
Diags.Reset();
// Disable warnings because computing the CFG might crash.
Diags.setIgnoreAllWarnings(true);
return true;
}
};
return std::make_unique<Consumer>();
}
};
// Should not crash
EXPECT_FALSE(
runToolOnCode(std::make_unique<ResetDiagnosticAction>(),
"struct Foo { Foo(int); ~Foo(); struct Fwd _fwd; };"
"void func() { long x; Foo f(x); }"));
}
} // end namespace tooling
} // end namespace clang