775 lines
28 KiB
C++
775 lines
28 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/AST/ASTConsumer.h"
|
||
|
#include "clang/AST/DeclCXX.h"
|
||
|
#include "clang/AST/DeclGroup.h"
|
||
|
#include "clang/Frontend/ASTUnit.h"
|
||
|
#include "clang/Frontend/CompilerInstance.h"
|
||
|
#include "clang/Frontend/FrontendAction.h"
|
||
|
#include "clang/Frontend/FrontendActions.h"
|
||
|
#include "clang/Tooling/ArgumentsAdjusters.h"
|
||
|
#include "clang/Tooling/CompilationDatabase.h"
|
||
|
#include "clang/Tooling/Tooling.h"
|
||
|
#include "llvm/ADT/STLExtras.h"
|
||
|
#include "llvm/ADT/StringRef.h"
|
||
|
#include "llvm/Support/Path.h"
|
||
|
#include "llvm/Support/TargetRegistry.h"
|
||
|
#include "llvm/Support/TargetSelect.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());
|
||
|
}
|
||
|
|
||
|
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"));
|
||
|
}
|
||
|
|
||
|
namespace {
|
||
|
/// Find a target name such that looking for it in TargetRegistry by that name
|
||
|
/// returns the same target. We expect that there is at least one target
|
||
|
/// configured with this property.
|
||
|
std::string getAnyTarget() {
|
||
|
llvm::InitializeAllTargets();
|
||
|
for (const auto &Target : llvm::TargetRegistry::targets()) {
|
||
|
std::string Error;
|
||
|
StringRef TargetName(Target.getName());
|
||
|
if (TargetName == "x86-64")
|
||
|
TargetName = "x86_64";
|
||
|
if (llvm::TargetRegistry::lookupTarget(std::string(TargetName), Error) ==
|
||
|
&Target) {
|
||
|
return std::string(TargetName);
|
||
|
}
|
||
|
}
|
||
|
return "";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
TEST(addTargetAndModeForProgramName, AddsTargetAndMode) {
|
||
|
std::string Target = getAnyTarget();
|
||
|
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) {
|
||
|
std::string Target = getAnyTarget();
|
||
|
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) {
|
||
|
std::string Target = getAnyTarget();
|
||
|
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) {
|
||
|
std::string Target = getAnyTarget();
|
||
|
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
|