blob: 9f9ac11a93eb85a8736833ed290b22646a1d3267 [file] [log] [blame]
//===--- FindHeadersTest.cpp ----------------------------------------------===//
//
// 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 "AnalysisInternal.h"
#include "TypesInternal.h"
#include "clang-include-cleaner/Analysis.h"
#include "clang-include-cleaner/Record.h"
#include "clang-include-cleaner/Types.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/FileEntry.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/LLVM.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Testing/TestAST.h"
#include "clang/Tooling/Inclusions/StandardLibrary.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <cassert>
#include <memory>
namespace clang::include_cleaner {
namespace {
using testing::ElementsAre;
using testing::UnorderedElementsAre;
std::string guard(llvm::StringRef Code) {
return "#pragma once\n" + Code.str();
}
class FindHeadersTest : public testing::Test {
protected:
TestInputs Inputs;
PragmaIncludes PI;
std::unique_ptr<TestAST> AST;
FindHeadersTest() {
Inputs.MakeAction = [this] {
struct Hook : public SyntaxOnlyAction {
public:
Hook(PragmaIncludes *Out) : Out(Out) {}
bool BeginSourceFileAction(clang::CompilerInstance &CI) override {
Out->record(CI);
return true;
}
PragmaIncludes *Out;
};
return std::make_unique<Hook>(&PI);
};
}
void buildAST() { AST = std::make_unique<TestAST>(Inputs); }
llvm::SmallVector<Hinted<Header>> findHeaders(llvm::StringRef FileName) {
return include_cleaner::findHeaders(
AST->sourceManager().translateFileLineCol(
AST->fileManager().getFile(FileName).get(),
/*Line=*/1, /*Col=*/1),
AST->sourceManager(), &PI);
}
const FileEntry *physicalHeader(llvm::StringRef FileName) {
return AST->fileManager().getFile(FileName).get();
};
};
TEST_F(FindHeadersTest, IWYUPrivateToPublic) {
Inputs.Code = R"cpp(
#include "private.h"
)cpp";
Inputs.ExtraFiles["private.h"] = guard(R"cpp(
// IWYU pragma: private, include "path/public.h"
)cpp");
buildAST();
EXPECT_THAT(findHeaders("private.h"),
UnorderedElementsAre(physicalHeader("private.h"),
Header("\"path/public.h\"")));
}
TEST_F(FindHeadersTest, IWYUExport) {
Inputs.Code = R"cpp(
#include "exporter.h"
)cpp";
Inputs.ExtraFiles["exporter.h"] = guard(R"cpp(
#include "exported1.h" // IWYU pragma: export
// IWYU pragma: begin_exports
#include "exported2.h"
// IWYU pragma: end_exports
#include "normal.h"
)cpp");
Inputs.ExtraFiles["exported1.h"] = guard("");
Inputs.ExtraFiles["exported2.h"] = guard("");
Inputs.ExtraFiles["normal.h"] = guard("");
buildAST();
EXPECT_THAT(findHeaders("exported1.h"),
UnorderedElementsAre(physicalHeader("exported1.h"),
physicalHeader("exporter.h")));
EXPECT_THAT(findHeaders("exported2.h"),
UnorderedElementsAre(physicalHeader("exported2.h"),
physicalHeader("exporter.h")));
EXPECT_THAT(findHeaders("normal.h"),
UnorderedElementsAre(physicalHeader("normal.h")));
EXPECT_THAT(findHeaders("exporter.h"),
UnorderedElementsAre(physicalHeader("exporter.h")));
}
TEST_F(FindHeadersTest, IWYUExportForStandardHeaders) {
Inputs.Code = R"cpp(
#include "exporter.h"
)cpp";
Inputs.ExtraFiles["exporter.h"] = guard(R"cpp(
#include <string> // IWYU pragma: export
)cpp");
Inputs.ExtraFiles["string"] = guard("");
Inputs.ExtraArgs.push_back("-isystem.");
buildAST();
tooling::stdlib::Symbol StdString =
*tooling::stdlib::Symbol::named("std::", "string");
EXPECT_THAT(
include_cleaner::findHeaders(StdString, AST->sourceManager(), &PI),
UnorderedElementsAre(physicalHeader("exporter.h"), StdString.header()));
}
TEST_F(FindHeadersTest, SelfContained) {
Inputs.Code = R"cpp(
#include "header.h"
)cpp";
Inputs.ExtraFiles["header.h"] = guard(R"cpp(
#include "fragment.inc"
)cpp");
Inputs.ExtraFiles["fragment.inc"] = "";
buildAST();
EXPECT_THAT(findHeaders("fragment.inc"),
UnorderedElementsAre(physicalHeader("fragment.inc"),
physicalHeader("header.h")));
}
TEST_F(FindHeadersTest, NonSelfContainedTraversePrivate) {
Inputs.Code = R"cpp(
#include "header.h"
)cpp";
Inputs.ExtraFiles["header.h"] = guard(R"cpp(
#include "fragment.inc"
)cpp");
Inputs.ExtraFiles["fragment.inc"] = R"cpp(
// IWYU pragma: private, include "public.h"
)cpp";
buildAST();
// There is a IWYU private mapping in the non self-contained header, verify
// that we don't emit its includer.
EXPECT_THAT(findHeaders("fragment.inc"),
UnorderedElementsAre(physicalHeader("fragment.inc"),
Header("\"public.h\"")));
}
TEST_F(FindHeadersTest, NonSelfContainedTraverseExporter) {
Inputs.Code = R"cpp(
#include "exporter.h"
)cpp";
Inputs.ExtraFiles["exporter.h"] = guard(R"cpp(
#include "exported.h" // IWYU pragma: export
)cpp");
Inputs.ExtraFiles["exported.h"] = guard(R"cpp(
#include "fragment.inc"
)cpp");
Inputs.ExtraFiles["fragment.inc"] = "";
buildAST();
// Verify that we emit exporters for each header on the path.
EXPECT_THAT(findHeaders("fragment.inc"),
UnorderedElementsAre(physicalHeader("fragment.inc"),
physicalHeader("exported.h"),
physicalHeader("exporter.h")));
}
TEST_F(FindHeadersTest, TargetIsExpandedFromMacroInHeader) {
struct CustomVisitor : RecursiveASTVisitor<CustomVisitor> {
const Decl *Out = nullptr;
bool VisitNamedDecl(const NamedDecl *ND) {
if (ND->getName() == "FLAG_foo" || ND->getName() == "Foo") {
EXPECT_TRUE(Out == nullptr);
Out = ND;
}
return true;
}
};
struct {
llvm::StringRef MacroHeader;
llvm::StringRef DeclareHeader;
} TestCases[] = {
{/*MacroHeader=*/R"cpp(
#define DEFINE_CLASS(name) class name {};
)cpp",
/*DeclareHeader=*/R"cpp(
#include "macro.h"
DEFINE_CLASS(Foo)
)cpp"},
{/*MacroHeader=*/R"cpp(
#define DEFINE_Foo class Foo {};
)cpp",
/*DeclareHeader=*/R"cpp(
#include "macro.h"
DEFINE_Foo
)cpp"},
{/*MacroHeader=*/R"cpp(
#define DECLARE_FLAGS(name) extern int FLAG_##name
)cpp",
/*DeclareHeader=*/R"cpp(
#include "macro.h"
DECLARE_FLAGS(foo);
)cpp"},
};
for (const auto &T : TestCases) {
Inputs.Code = R"cpp(#include "declare.h")cpp";
Inputs.ExtraFiles["declare.h"] = guard(T.DeclareHeader);
Inputs.ExtraFiles["macro.h"] = guard(T.MacroHeader);
buildAST();
CustomVisitor Visitor;
Visitor.TraverseDecl(AST->context().getTranslationUnitDecl());
auto Headers = clang::include_cleaner::findHeaders(
Visitor.Out->getLocation(), AST->sourceManager(),
/*PragmaIncludes=*/nullptr);
EXPECT_THAT(Headers, UnorderedElementsAre(physicalHeader("declare.h")));
}
}
MATCHER_P2(HintedHeader, Header, Hint, "") {
return std::tie(arg.Hint, arg) == std::tie(Hint, Header);
}
TEST_F(FindHeadersTest, PublicHeaderHint) {
Inputs.Code = R"cpp(
#include "public.h"
)cpp";
Inputs.ExtraFiles["public.h"] = guard(R"cpp(
#include "private.h"
#include "private.inc"
)cpp");
Inputs.ExtraFiles["private.h"] = guard(R"cpp(
// IWYU pragma: private
)cpp");
Inputs.ExtraFiles["private.inc"] = "";
buildAST();
// Non self-contained files and headers marked with IWYU private pragma
// shouldn't have PublicHeader hint.
EXPECT_THAT(
findHeaders("private.inc"),
UnorderedElementsAre(
HintedHeader(physicalHeader("private.inc"), Hints::OriginHeader),
HintedHeader(physicalHeader("public.h"), Hints::PublicHeader)));
EXPECT_THAT(findHeaders("private.h"),
UnorderedElementsAre(HintedHeader(physicalHeader("private.h"),
Hints::OriginHeader)));
}
TEST_F(FindHeadersTest, PreferredHeaderHint) {
Inputs.Code = R"cpp(
#include "private.h"
)cpp";
Inputs.ExtraFiles["private.h"] = guard(R"cpp(
// IWYU pragma: private, include "public.h"
)cpp");
buildAST();
// Headers explicitly marked should've preferred signal.
EXPECT_THAT(
findHeaders("private.h"),
UnorderedElementsAre(
HintedHeader(physicalHeader("private.h"), Hints::OriginHeader),
HintedHeader(Header("\"public.h\""),
Hints::PreferredHeader | Hints::PublicHeader)));
}
class HeadersForSymbolTest : public FindHeadersTest {
protected:
llvm::SmallVector<Header> headersFor(llvm::StringRef Name) {
struct Visitor : public RecursiveASTVisitor<Visitor> {
const NamedDecl *Out = nullptr;
llvm::StringRef Name;
Visitor(llvm::StringRef Name) : Name(Name) {}
bool VisitNamedDecl(const NamedDecl *ND) {
if (auto *TD = ND->getDescribedTemplate())
ND = TD;
if (ND->getName() == Name) {
EXPECT_TRUE(Out == nullptr || Out == ND->getCanonicalDecl())
<< "Found multiple matches for " << Name << ".";
Out = cast<NamedDecl>(ND->getCanonicalDecl());
}
return true;
}
};
Visitor V(Name);
V.TraverseDecl(AST->context().getTranslationUnitDecl());
if (!V.Out)
ADD_FAILURE() << "Couldn't find any decls named " << Name << ".";
assert(V.Out);
return headersForSymbol(*V.Out, AST->sourceManager(), &PI);
}
llvm::SmallVector<Header> headersForFoo() { return headersFor("foo"); }
};
TEST_F(HeadersForSymbolTest, Deduplicates) {
Inputs.Code = R"cpp(
#include "foo.h"
)cpp";
Inputs.ExtraFiles["foo.h"] = guard(R"cpp(
// IWYU pragma: private, include "foo.h"
void foo();
void foo();
)cpp");
buildAST();
EXPECT_THAT(
headersForFoo(),
UnorderedElementsAre(physicalHeader("foo.h"),
// FIXME: de-duplicate across different kinds.
Header("\"foo.h\"")));
}
TEST_F(HeadersForSymbolTest, RankByName) {
Inputs.Code = R"cpp(
#include "fox.h"
#include "bar.h"
)cpp";
Inputs.ExtraFiles["fox.h"] = guard(R"cpp(
void foo();
)cpp");
Inputs.ExtraFiles["bar.h"] = guard(R"cpp(
void foo();
)cpp");
buildAST();
EXPECT_THAT(headersForFoo(),
ElementsAre(physicalHeader("bar.h"), physicalHeader("fox.h")));
}
TEST_F(HeadersForSymbolTest, Ranking) {
// Sorting is done over (canonical, public, complete, origin)-tuple.
Inputs.Code = R"cpp(
#include "private.h"
#include "public.h"
#include "public_complete.h"
#include "exporter.h"
)cpp";
Inputs.ExtraFiles["public.h"] = guard(R"cpp(
struct foo;
)cpp");
Inputs.ExtraFiles["private.h"] = guard(R"cpp(
// IWYU pragma: private, include "canonical.h"
struct foo;
)cpp");
Inputs.ExtraFiles["exporter.h"] = guard(R"cpp(
#include "private.h" // IWYU pragma: export
)cpp");
Inputs.ExtraFiles["public_complete.h"] = guard("struct foo {};");
buildAST();
EXPECT_THAT(headersForFoo(), ElementsAre(Header("\"canonical.h\""),
physicalHeader("public_complete.h"),
physicalHeader("public.h"),
physicalHeader("exporter.h"),
physicalHeader("private.h")));
}
TEST_F(HeadersForSymbolTest, PreferPublicOverComplete) {
Inputs.Code = R"cpp(
#include "complete_private.h"
#include "public.h"
)cpp";
Inputs.ExtraFiles["complete_private.h"] = guard(R"cpp(
// IWYU pragma: private
struct foo {};
)cpp");
Inputs.ExtraFiles["public.h"] = guard("struct foo;");
buildAST();
EXPECT_THAT(headersForFoo(),
ElementsAre(physicalHeader("public.h"),
physicalHeader("complete_private.h")));
}
TEST_F(HeadersForSymbolTest, PreferNameMatch) {
Inputs.Code = R"cpp(
#include "public_complete.h"
#include "test/foo.fwd.h"
)cpp";
Inputs.ExtraFiles["public_complete.h"] = guard(R"cpp(
struct foo {};
)cpp");
Inputs.ExtraFiles["test/foo.fwd.h"] = guard("struct foo;");
buildAST();
EXPECT_THAT(headersForFoo(),
ElementsAre(physicalHeader("test/foo.fwd.h"),
physicalHeader("public_complete.h")));
}
TEST_F(HeadersForSymbolTest, MainFile) {
Inputs.Code = R"cpp(
#include "public_complete.h"
struct foo;
)cpp";
Inputs.ExtraFiles["public_complete.h"] = guard(R"cpp(
struct foo {};
)cpp");
buildAST();
auto &SM = AST->sourceManager();
// FIXME: Symbols provided by main file should be treated specially.
EXPECT_THAT(headersForFoo(),
ElementsAre(physicalHeader("public_complete.h"),
Header(SM.getFileEntryForID(SM.getMainFileID()))));
}
TEST_F(HeadersForSymbolTest, PreferExporterOfPrivate) {
Inputs.Code = R"cpp(
#include "private.h"
#include "exporter.h"
)cpp";
Inputs.ExtraFiles["private.h"] = guard(R"cpp(
// IWYU pragma: private
struct foo {};
)cpp");
Inputs.ExtraFiles["exporter.h"] = guard(R"cpp(
#include "private.h" // IWYU pragma: export
)cpp");
buildAST();
EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("exporter.h"),
physicalHeader("private.h")));
}
TEST_F(HeadersForSymbolTest, ExporterIsDownRanked) {
Inputs.Code = R"cpp(
#include "exporter.h"
#include "zoo.h"
)cpp";
// Deliberately named as zoo to make sure it doesn't get name-match boost and
// also gets lexicographically bigger order than "exporter".
Inputs.ExtraFiles["zoo.h"] = guard(R"cpp(
struct foo {};
)cpp");
Inputs.ExtraFiles["exporter.h"] = guard(R"cpp(
#include "zoo.h" // IWYU pragma: export
)cpp");
buildAST();
EXPECT_THAT(headersForFoo(), ElementsAre(physicalHeader("zoo.h"),
physicalHeader("exporter.h")));
}
TEST_F(HeadersForSymbolTest, PreferPublicOverNameMatchOnPrivate) {
Inputs.Code = R"cpp(
#include "foo.h"
)cpp";
Inputs.ExtraFiles["foo.h"] = guard(R"cpp(
// IWYU pragma: private, include "public.h"
struct foo {};
)cpp");
buildAST();
EXPECT_THAT(headersForFoo(), ElementsAre(Header(StringRef("\"public.h\"")),
physicalHeader("foo.h")));
}
TEST_F(HeadersForSymbolTest, AmbiguousStdSymbols) {
struct {
llvm::StringRef Code;
llvm::StringRef Name;
llvm::StringRef ExpectedHeader;
} TestCases[] = {
{
R"cpp(
namespace std {
template <typename InputIt, typename OutputIt>
constexpr OutputIt move(InputIt first, InputIt last, OutputIt dest);
})cpp",
"move",
"<algorithm>",
},
{
R"cpp(
namespace std {
template<typename T> constexpr T move(T&& t) noexcept;
})cpp",
"move",
"<utility>",
},
{
R"cpp(
namespace std {
template<class ForwardIt, class T>
ForwardIt remove(ForwardIt first, ForwardIt last, const T& value);
})cpp",
"remove",
"<algorithm>",
},
{
"namespace std { int remove(const char*); }",
"remove",
"<cstdio>",
},
};
for (const auto &T : TestCases) {
Inputs.Code = T.Code;
buildAST();
EXPECT_THAT(headersFor(T.Name),
UnorderedElementsAre(
Header(*tooling::stdlib::Header::named(T.ExpectedHeader))));
}
}
TEST_F(HeadersForSymbolTest, StandardHeaders) {
Inputs.Code = "void assert();";
buildAST();
EXPECT_THAT(
headersFor("assert"),
// Respect the ordering from the stdlib mapping.
UnorderedElementsAre(tooling::stdlib::Header::named("<cassert>"),
tooling::stdlib::Header::named("<assert.h>")));
}
} // namespace
} // namespace clang::include_cleaner