mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-18 14:56:44 +00:00

With the goal of eventually being able to make `-Wreturn-type` default to an error in all language modes, this is a follow-up to #123464 and updates even more tests, mainly clang-tidy and clangd tests.
799 lines
24 KiB
C++
799 lines
24 KiB
C++
//===-- ParsedASTTests.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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// These tests cover clangd's logic to build a TU, which generally uses the APIs
|
|
// in ParsedAST and Preamble, via the TestTU helper.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "../../clang-tidy/ClangTidyCheck.h"
|
|
#include "AST.h"
|
|
#include "Compiler.h"
|
|
#include "Config.h"
|
|
#include "Diagnostics.h"
|
|
#include "Headers.h"
|
|
#include "ParsedAST.h"
|
|
#include "Preamble.h"
|
|
#include "SourceCode.h"
|
|
#include "TestFS.h"
|
|
#include "TestTU.h"
|
|
#include "TidyProvider.h"
|
|
#include "support/Context.h"
|
|
#include "clang/AST/DeclTemplate.h"
|
|
#include "clang/Basic/FileEntry.h"
|
|
#include "clang/Basic/SourceLocation.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "clang/Basic/TokenKinds.h"
|
|
#include "clang/Tooling/Syntax/Tokens.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/Testing/Annotations/Annotations.h"
|
|
#include "llvm/Testing/Support/Error.h"
|
|
#include "gmock/gmock-matchers.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include <memory>
|
|
#include <string_view>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
namespace {
|
|
|
|
using ::testing::AllOf;
|
|
using ::testing::Contains;
|
|
using ::testing::ElementsAre;
|
|
using ::testing::ElementsAreArray;
|
|
using ::testing::IsEmpty;
|
|
|
|
MATCHER_P(declNamed, Name, "") {
|
|
if (NamedDecl *ND = dyn_cast<NamedDecl>(arg))
|
|
if (ND->getName() == Name)
|
|
return true;
|
|
if (auto *Stream = result_listener->stream()) {
|
|
llvm::raw_os_ostream OS(*Stream);
|
|
arg->dump(OS);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
MATCHER_P(declKind, Kind, "") {
|
|
if (NamedDecl *ND = dyn_cast<NamedDecl>(arg))
|
|
if (ND->getDeclKindName() == llvm::StringRef(Kind))
|
|
return true;
|
|
if (auto *Stream = result_listener->stream()) {
|
|
llvm::raw_os_ostream OS(*Stream);
|
|
arg->dump(OS);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Matches if the Decl has template args equal to ArgName. If the decl is a
|
|
// NamedDecl and ArgName is an empty string it also matches.
|
|
MATCHER_P(withTemplateArgs, ArgName, "") {
|
|
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(arg)) {
|
|
if (const auto *Args = FD->getTemplateSpecializationArgs()) {
|
|
std::string SpecializationArgs;
|
|
// Without the PrintingPolicy "bool" will be printed as "_Bool".
|
|
LangOptions LO;
|
|
PrintingPolicy Policy(LO);
|
|
Policy.adjustForCPlusPlus();
|
|
for (const auto &Arg : Args->asArray()) {
|
|
if (SpecializationArgs.size() > 0)
|
|
SpecializationArgs += ",";
|
|
SpecializationArgs += Arg.getAsType().getAsString(Policy);
|
|
}
|
|
if (Args->size() == 0)
|
|
return ArgName == SpecializationArgs;
|
|
return ArgName == "<" + SpecializationArgs + ">";
|
|
}
|
|
}
|
|
if (const NamedDecl *ND = dyn_cast<NamedDecl>(arg))
|
|
return printTemplateSpecializationArgs(*ND) == ArgName;
|
|
return false;
|
|
}
|
|
|
|
MATCHER_P(pragmaTrivia, P, "") { return arg.Trivia == P; }
|
|
|
|
MATCHER(eqInc, "") {
|
|
Inclusion Actual = testing::get<0>(arg);
|
|
Inclusion Expected = testing::get<1>(arg);
|
|
return std::tie(Actual.HashLine, Actual.Written) ==
|
|
std::tie(Expected.HashLine, Expected.Written);
|
|
}
|
|
|
|
TEST(ParsedASTTest, TopLevelDecls) {
|
|
TestTU TU;
|
|
TU.HeaderCode = R"(
|
|
int header1();
|
|
int header2;
|
|
)";
|
|
TU.Code = R"cpp(
|
|
int main();
|
|
template <typename> bool X = true;
|
|
)cpp";
|
|
auto AST = TU.build();
|
|
EXPECT_THAT(AST.getLocalTopLevelDecls(),
|
|
testing::UnorderedElementsAreArray(
|
|
{AllOf(declNamed("main"), declKind("Function")),
|
|
AllOf(declNamed("X"), declKind("VarTemplate"))}));
|
|
}
|
|
|
|
TEST(ParsedASTTest, DoesNotGetIncludedTopDecls) {
|
|
TestTU TU;
|
|
TU.HeaderCode = R"cpp(
|
|
#define LL void foo(){}
|
|
template<class T>
|
|
struct H {
|
|
H() {}
|
|
LL
|
|
};
|
|
)cpp";
|
|
TU.Code = R"cpp(
|
|
int main() {
|
|
H<int> h;
|
|
h.foo();
|
|
}
|
|
)cpp";
|
|
auto AST = TU.build();
|
|
EXPECT_THAT(AST.getLocalTopLevelDecls(), ElementsAre(declNamed("main")));
|
|
}
|
|
|
|
TEST(ParsedASTTest, DoesNotGetImplicitTemplateTopDecls) {
|
|
TestTU TU;
|
|
TU.Code = R"cpp(
|
|
template<typename T>
|
|
void f(T) {}
|
|
void s() {
|
|
f(10UL);
|
|
}
|
|
)cpp";
|
|
|
|
auto AST = TU.build();
|
|
EXPECT_THAT(AST.getLocalTopLevelDecls(),
|
|
ElementsAre(declNamed("f"), declNamed("s")));
|
|
}
|
|
|
|
TEST(ParsedASTTest,
|
|
GetsExplicitInstantiationAndSpecializationTemplateTopDecls) {
|
|
TestTU TU;
|
|
TU.Code = R"cpp(
|
|
template <typename T>
|
|
void f(T) {}
|
|
template<>
|
|
void f(bool);
|
|
template void f(double);
|
|
|
|
template <class T>
|
|
struct V {};
|
|
template<class T>
|
|
struct V<T*> {};
|
|
template <>
|
|
struct V<bool> {};
|
|
|
|
template<class T>
|
|
T foo = T(10);
|
|
int i = foo<int>;
|
|
double d = foo<double>;
|
|
|
|
template <class T>
|
|
int foo<T*> = 0;
|
|
template <>
|
|
int foo<bool> = 0;
|
|
)cpp";
|
|
|
|
auto AST = TU.build();
|
|
EXPECT_THAT(
|
|
AST.getLocalTopLevelDecls(),
|
|
ElementsAreArray({AllOf(declNamed("f"), withTemplateArgs("")),
|
|
AllOf(declNamed("f"), withTemplateArgs("<bool>")),
|
|
AllOf(declNamed("f"), withTemplateArgs("<double>")),
|
|
AllOf(declNamed("V"), withTemplateArgs("")),
|
|
AllOf(declNamed("V"), withTemplateArgs("<T *>")),
|
|
AllOf(declNamed("V"), withTemplateArgs("<bool>")),
|
|
AllOf(declNamed("foo"), withTemplateArgs("")),
|
|
AllOf(declNamed("i"), withTemplateArgs("")),
|
|
AllOf(declNamed("d"), withTemplateArgs("")),
|
|
AllOf(declNamed("foo"), withTemplateArgs("<T *>")),
|
|
AllOf(declNamed("foo"), withTemplateArgs("<bool>"))}));
|
|
}
|
|
|
|
TEST(ParsedASTTest, IgnoresDelayedTemplateParsing) {
|
|
auto TU = TestTU::withCode(R"cpp(
|
|
template <typename T> void xxx() {
|
|
int yyy = 0;
|
|
}
|
|
)cpp");
|
|
TU.ExtraArgs.push_back("-fdelayed-template-parsing");
|
|
auto AST = TU.build();
|
|
EXPECT_EQ(Decl::Var, findUnqualifiedDecl(AST, "yyy").getKind());
|
|
}
|
|
|
|
TEST(ParsedASTTest, TokensAfterPreamble) {
|
|
TestTU TU;
|
|
TU.AdditionalFiles["foo.h"] = R"(
|
|
int foo();
|
|
)";
|
|
TU.Code = R"cpp(
|
|
#include "foo.h"
|
|
first_token;
|
|
void test() {
|
|
// error-ok: invalid syntax, just examining token stream
|
|
}
|
|
last_token
|
|
)cpp";
|
|
auto AST = TU.build();
|
|
const syntax::TokenBuffer &T = AST.getTokens();
|
|
const auto &SM = AST.getSourceManager();
|
|
|
|
ASSERT_GT(T.expandedTokens().size(), 2u);
|
|
// Check first token after the preamble.
|
|
EXPECT_EQ(T.expandedTokens().front().text(SM), "first_token");
|
|
// Last token is always 'eof'.
|
|
EXPECT_EQ(T.expandedTokens().back().kind(), tok::eof);
|
|
// Check the token before 'eof'.
|
|
EXPECT_EQ(T.expandedTokens().drop_back().back().text(SM), "last_token");
|
|
|
|
// The spelled tokens for the main file should have everything.
|
|
auto Spelled = T.spelledTokens(SM.getMainFileID());
|
|
ASSERT_FALSE(Spelled.empty());
|
|
EXPECT_EQ(Spelled.front().kind(), tok::hash);
|
|
EXPECT_EQ(Spelled.back().text(SM), "last_token");
|
|
}
|
|
|
|
TEST(ParsedASTTest, NoCrashOnTokensWithTidyCheck) {
|
|
TestTU TU;
|
|
// this check runs the preprocessor, we need to make sure it does not break
|
|
// our recording logic.
|
|
TU.ClangTidyProvider = addTidyChecks("modernize-use-trailing-return-type");
|
|
TU.Code = "inline int foo() { return 0; }";
|
|
|
|
auto AST = TU.build();
|
|
const syntax::TokenBuffer &T = AST.getTokens();
|
|
const auto &SM = AST.getSourceManager();
|
|
|
|
ASSERT_GT(T.expandedTokens().size(), 7u);
|
|
// Check first token after the preamble.
|
|
EXPECT_EQ(T.expandedTokens().front().text(SM), "inline");
|
|
// Last token is always 'eof'.
|
|
EXPECT_EQ(T.expandedTokens().back().kind(), tok::eof);
|
|
// Check the token before 'eof'.
|
|
EXPECT_EQ(T.expandedTokens().drop_back().back().text(SM), "}");
|
|
}
|
|
|
|
TEST(ParsedASTTest, CanBuildInvocationWithUnknownArgs) {
|
|
MockFS FS;
|
|
FS.Files = {{testPath("foo.cpp"), "void test() {}"}};
|
|
// Unknown flags should not prevent a build of compiler invocation.
|
|
ParseInputs Inputs;
|
|
Inputs.TFS = &FS;
|
|
Inputs.CompileCommand.CommandLine = {"clang", "-fsome-unknown-flag",
|
|
testPath("foo.cpp")};
|
|
IgnoreDiagnostics IgnoreDiags;
|
|
EXPECT_NE(buildCompilerInvocation(Inputs, IgnoreDiags), nullptr);
|
|
|
|
// Unknown forwarded to -cc1 should not a failure either.
|
|
Inputs.CompileCommand.CommandLine = {
|
|
"clang", "-Xclang", "-fsome-unknown-flag", testPath("foo.cpp")};
|
|
EXPECT_NE(buildCompilerInvocation(Inputs, IgnoreDiags), nullptr);
|
|
}
|
|
|
|
TEST(ParsedASTTest, CollectsMainFileMacroExpansions) {
|
|
llvm::Annotations TestCase(R"cpp(
|
|
#define ^MACRO_ARGS(X, Y) X Y
|
|
// - preamble ends
|
|
^ID(int A);
|
|
// Macro arguments included.
|
|
^MACRO_ARGS(^MACRO_ARGS(^MACRO_EXP(int), E), ^ID(= 2));
|
|
|
|
// Macro names inside other macros not included.
|
|
#define ^MACRO_ARGS2(X, Y) X Y
|
|
#define ^FOO BAR
|
|
#define ^BAR 1
|
|
int F = ^FOO;
|
|
|
|
// Macros from token concatenations not included.
|
|
#define ^CONCAT(X) X##A()
|
|
#define ^PREPEND(X) MACRO##X()
|
|
#define ^MACROA() 123
|
|
int G = ^CONCAT(MACRO);
|
|
int H = ^PREPEND(A);
|
|
|
|
// Macros included not from preamble not included.
|
|
#include "foo.inc"
|
|
|
|
int printf(const char*, ...);
|
|
void exit(int);
|
|
#define ^assert(COND) if (!(COND)) { printf("%s", #COND); exit(0); }
|
|
|
|
void test() {
|
|
// Includes macro expansions in arguments that are expressions
|
|
^assert(0 <= ^BAR);
|
|
}
|
|
|
|
#ifdef ^UNDEFINED
|
|
#endif
|
|
|
|
#define ^MULTIPLE_DEFINITION 1
|
|
#undef ^MULTIPLE_DEFINITION
|
|
|
|
#define ^MULTIPLE_DEFINITION 2
|
|
#undef ^MULTIPLE_DEFINITION
|
|
)cpp");
|
|
auto TU = TestTU::withCode(TestCase.code());
|
|
TU.HeaderCode = R"cpp(
|
|
#define ID(X) X
|
|
#define MACRO_EXP(X) ID(X)
|
|
MACRO_EXP(int B);
|
|
)cpp";
|
|
TU.AdditionalFiles["foo.inc"] = R"cpp(
|
|
int C = ID(1);
|
|
#define DEF 1
|
|
int D = DEF;
|
|
)cpp";
|
|
ParsedAST AST = TU.build();
|
|
std::vector<size_t> MacroExpansionPositions;
|
|
for (const auto &SIDToRefs : AST.getMacros().MacroRefs) {
|
|
for (const auto &R : SIDToRefs.second)
|
|
MacroExpansionPositions.push_back(R.StartOffset);
|
|
}
|
|
for (const auto &R : AST.getMacros().UnknownMacros)
|
|
MacroExpansionPositions.push_back(R.StartOffset);
|
|
EXPECT_THAT(MacroExpansionPositions,
|
|
testing::UnorderedElementsAreArray(TestCase.points()));
|
|
}
|
|
|
|
MATCHER_P(withFileName, Inc, "") { return arg.FileName == Inc; }
|
|
|
|
TEST(ParsedASTTest, PatchesAdditionalIncludes) {
|
|
llvm::StringLiteral ModifiedContents = R"cpp(
|
|
#include "baz.h"
|
|
#include "foo.h"
|
|
#include "sub/aux.h"
|
|
void bar() {
|
|
foo();
|
|
baz();
|
|
aux();
|
|
})cpp";
|
|
// Build expected ast with symbols coming from headers.
|
|
TestTU TU;
|
|
TU.Filename = "foo.cpp";
|
|
TU.AdditionalFiles["foo.h"] = "void foo();";
|
|
TU.AdditionalFiles["sub/baz.h"] = "void baz();";
|
|
TU.AdditionalFiles["sub/aux.h"] = "void aux();";
|
|
TU.ExtraArgs = {"-I" + testPath("sub")};
|
|
TU.Code = ModifiedContents.str();
|
|
auto ExpectedAST = TU.build();
|
|
|
|
// Build preamble with no includes.
|
|
TU.Code = "";
|
|
StoreDiags Diags;
|
|
MockFS FS;
|
|
auto Inputs = TU.inputs(FS);
|
|
auto CI = buildCompilerInvocation(Inputs, Diags);
|
|
auto EmptyPreamble =
|
|
buildPreamble(testPath("foo.cpp"), *CI, Inputs, true, nullptr);
|
|
ASSERT_TRUE(EmptyPreamble);
|
|
EXPECT_THAT(EmptyPreamble->Includes.MainFileIncludes, IsEmpty());
|
|
|
|
// Now build an AST using empty preamble and ensure patched includes worked.
|
|
TU.Code = ModifiedContents.str();
|
|
Inputs = TU.inputs(FS);
|
|
auto PatchedAST = ParsedAST::build(testPath("foo.cpp"), Inputs, std::move(CI),
|
|
{}, EmptyPreamble);
|
|
ASSERT_TRUE(PatchedAST);
|
|
|
|
// Ensure source location information is correct, including resolved paths.
|
|
EXPECT_THAT(PatchedAST->getIncludeStructure().MainFileIncludes,
|
|
testing::Pointwise(
|
|
eqInc(), ExpectedAST.getIncludeStructure().MainFileIncludes));
|
|
// Ensure file proximity signals are correct.
|
|
auto &SM = PatchedAST->getSourceManager();
|
|
auto &FM = SM.getFileManager();
|
|
// Copy so that we can use operator[] to get the children.
|
|
IncludeStructure Includes = PatchedAST->getIncludeStructure();
|
|
auto MainFE = FM.getOptionalFileRef(testPath("foo.cpp"));
|
|
ASSERT_TRUE(MainFE);
|
|
auto MainID = Includes.getID(*MainFE);
|
|
auto AuxFE = FM.getOptionalFileRef(testPath("sub/aux.h"));
|
|
ASSERT_TRUE(AuxFE);
|
|
auto AuxID = Includes.getID(*AuxFE);
|
|
EXPECT_THAT(Includes.IncludeChildren[*MainID], Contains(*AuxID));
|
|
}
|
|
|
|
TEST(ParsedASTTest, PatchesDeletedIncludes) {
|
|
TestTU TU;
|
|
TU.Filename = "foo.cpp";
|
|
TU.Code = "";
|
|
auto ExpectedAST = TU.build();
|
|
|
|
// Build preamble with no includes.
|
|
TU.Code = R"cpp(#include <foo.h>)cpp";
|
|
StoreDiags Diags;
|
|
MockFS FS;
|
|
auto Inputs = TU.inputs(FS);
|
|
auto CI = buildCompilerInvocation(Inputs, Diags);
|
|
auto BaselinePreamble =
|
|
buildPreamble(testPath("foo.cpp"), *CI, Inputs, true, nullptr);
|
|
ASSERT_TRUE(BaselinePreamble);
|
|
EXPECT_THAT(BaselinePreamble->Includes.MainFileIncludes,
|
|
ElementsAre(testing::Field(&Inclusion::Written, "<foo.h>")));
|
|
|
|
// Now build an AST using additional includes and check that locations are
|
|
// correctly parsed.
|
|
TU.Code = "";
|
|
Inputs = TU.inputs(FS);
|
|
auto PatchedAST = ParsedAST::build(testPath("foo.cpp"), Inputs, std::move(CI),
|
|
{}, BaselinePreamble);
|
|
ASSERT_TRUE(PatchedAST);
|
|
|
|
// Ensure source location information is correct.
|
|
EXPECT_THAT(PatchedAST->getIncludeStructure().MainFileIncludes,
|
|
testing::Pointwise(
|
|
eqInc(), ExpectedAST.getIncludeStructure().MainFileIncludes));
|
|
// Ensure file proximity signals are correct.
|
|
auto &SM = ExpectedAST.getSourceManager();
|
|
auto &FM = SM.getFileManager();
|
|
// Copy so that we can getOrCreateID().
|
|
IncludeStructure Includes = ExpectedAST.getIncludeStructure();
|
|
auto MainFE = FM.getFileRef(testPath("foo.cpp"));
|
|
ASSERT_THAT_EXPECTED(MainFE, llvm::Succeeded());
|
|
auto MainID = Includes.getOrCreateID(*MainFE);
|
|
auto &PatchedFM = PatchedAST->getSourceManager().getFileManager();
|
|
IncludeStructure PatchedIncludes = PatchedAST->getIncludeStructure();
|
|
auto PatchedMainFE = PatchedFM.getFileRef(testPath("foo.cpp"));
|
|
ASSERT_THAT_EXPECTED(PatchedMainFE, llvm::Succeeded());
|
|
auto PatchedMainID = PatchedIncludes.getOrCreateID(*PatchedMainFE);
|
|
EXPECT_EQ(Includes.includeDepth(MainID)[MainID],
|
|
PatchedIncludes.includeDepth(PatchedMainID)[PatchedMainID]);
|
|
}
|
|
|
|
// Returns Code guarded by #ifndef guards
|
|
std::string guard(llvm::StringRef Code) {
|
|
static int GuardID = 0;
|
|
std::string GuardName = ("GUARD_" + llvm::Twine(++GuardID)).str();
|
|
return llvm::formatv("#ifndef {0}\n#define {0}\n{1}\n#endif\n", GuardName,
|
|
Code);
|
|
}
|
|
|
|
std::string once(llvm::StringRef Code) {
|
|
return llvm::formatv("#pragma once\n{0}\n", Code);
|
|
}
|
|
|
|
bool mainIsGuarded(const ParsedAST &AST) {
|
|
const auto &SM = AST.getSourceManager();
|
|
OptionalFileEntryRef MainFE = SM.getFileEntryRefForID(SM.getMainFileID());
|
|
return AST.getPreprocessor()
|
|
.getHeaderSearchInfo()
|
|
.isFileMultipleIncludeGuarded(*MainFE);
|
|
}
|
|
|
|
MATCHER_P(diag, Desc, "") {
|
|
return llvm::StringRef(arg.Message).contains(Desc);
|
|
}
|
|
|
|
// Check our understanding of whether the main file is header guarded or not.
|
|
TEST(ParsedASTTest, HeaderGuards) {
|
|
TestTU TU;
|
|
TU.ImplicitHeaderGuard = false;
|
|
|
|
TU.Code = ";";
|
|
EXPECT_FALSE(mainIsGuarded(TU.build()));
|
|
|
|
TU.Code = guard(";");
|
|
EXPECT_TRUE(mainIsGuarded(TU.build()));
|
|
|
|
TU.Code = once(";");
|
|
EXPECT_TRUE(mainIsGuarded(TU.build()));
|
|
|
|
TU.Code = R"cpp(
|
|
;
|
|
#pragma once
|
|
)cpp";
|
|
EXPECT_FALSE(mainIsGuarded(TU.build())); // FIXME: true
|
|
|
|
TU.Code = R"cpp(
|
|
;
|
|
#ifndef GUARD
|
|
#define GUARD
|
|
;
|
|
#endif
|
|
)cpp";
|
|
EXPECT_FALSE(mainIsGuarded(TU.build()));
|
|
}
|
|
|
|
// Check our handling of files that include themselves.
|
|
// Ideally we allow this if the file has header guards.
|
|
//
|
|
// Note: the semicolons (empty statements) are significant!
|
|
// - they force the preamble to end and the body to begin. Directives can have
|
|
// different effects in the preamble vs main file (which we try to hide).
|
|
// - if the preamble would otherwise cover the whole file, a trailing semicolon
|
|
// forces their sizes to be different. This is significant because the file
|
|
// size is part of the lookup key for HeaderFileInfo, and we don't want to
|
|
// rely on the preamble's HFI being looked up when parsing the main file.
|
|
TEST(ParsedASTTest, HeaderGuardsSelfInclude) {
|
|
// Disable include cleaner diagnostics to prevent them from interfering with
|
|
// other diagnostics.
|
|
Config Cfg;
|
|
Cfg.Diagnostics.MissingIncludes = Config::IncludesPolicy::None;
|
|
Cfg.Diagnostics.UnusedIncludes = Config::IncludesPolicy::None;
|
|
WithContextValue Ctx(Config::Key, std::move(Cfg));
|
|
|
|
TestTU TU;
|
|
TU.ImplicitHeaderGuard = false;
|
|
TU.Filename = "self.h";
|
|
|
|
TU.Code = R"cpp(
|
|
#include "self.h" // error-ok
|
|
;
|
|
)cpp";
|
|
auto AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(),
|
|
ElementsAre(diag("recursively when building a preamble")));
|
|
EXPECT_FALSE(mainIsGuarded(AST));
|
|
|
|
TU.Code = R"cpp(
|
|
;
|
|
#include "self.h" // error-ok
|
|
)cpp";
|
|
AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(), ElementsAre(diag("nested too deeply")));
|
|
EXPECT_FALSE(mainIsGuarded(AST));
|
|
|
|
TU.Code = R"cpp(
|
|
#pragma once
|
|
#include "self.h"
|
|
;
|
|
)cpp";
|
|
AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(), IsEmpty());
|
|
EXPECT_TRUE(mainIsGuarded(AST));
|
|
|
|
TU.Code = R"cpp(
|
|
#pragma once
|
|
;
|
|
#include "self.h"
|
|
)cpp";
|
|
AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(), IsEmpty());
|
|
EXPECT_TRUE(mainIsGuarded(AST));
|
|
|
|
TU.Code = R"cpp(
|
|
;
|
|
#pragma once
|
|
#include "self.h"
|
|
)cpp";
|
|
AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(), IsEmpty());
|
|
EXPECT_TRUE(mainIsGuarded(AST));
|
|
|
|
TU.Code = R"cpp(
|
|
#ifndef GUARD
|
|
#define GUARD
|
|
#include "self.h" // error-ok: FIXME, this would be nice to support
|
|
#endif
|
|
;
|
|
)cpp";
|
|
AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(),
|
|
ElementsAre(diag("recursively when building a preamble")));
|
|
EXPECT_TRUE(mainIsGuarded(AST));
|
|
|
|
TU.Code = R"cpp(
|
|
#ifndef GUARD
|
|
#define GUARD
|
|
;
|
|
#include "self.h"
|
|
#endif
|
|
)cpp";
|
|
AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(), IsEmpty());
|
|
EXPECT_TRUE(mainIsGuarded(AST));
|
|
|
|
// Guarded too late...
|
|
TU.Code = R"cpp(
|
|
#include "self.h" // error-ok
|
|
#ifndef GUARD
|
|
#define GUARD
|
|
;
|
|
#endif
|
|
)cpp";
|
|
AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(),
|
|
ElementsAre(diag("recursively when building a preamble")));
|
|
EXPECT_FALSE(mainIsGuarded(AST));
|
|
|
|
TU.Code = R"cpp(
|
|
#include "self.h" // error-ok
|
|
;
|
|
#ifndef GUARD
|
|
#define GUARD
|
|
#endif
|
|
)cpp";
|
|
AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(),
|
|
ElementsAre(diag("recursively when building a preamble")));
|
|
EXPECT_FALSE(mainIsGuarded(AST));
|
|
|
|
TU.Code = R"cpp(
|
|
;
|
|
#ifndef GUARD
|
|
#define GUARD
|
|
#include "self.h"
|
|
#endif
|
|
)cpp";
|
|
AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(), IsEmpty());
|
|
EXPECT_FALSE(mainIsGuarded(AST));
|
|
|
|
TU.Code = R"cpp(
|
|
#include "self.h" // error-ok
|
|
#pragma once
|
|
;
|
|
)cpp";
|
|
AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(),
|
|
ElementsAre(diag("recursively when building a preamble")));
|
|
EXPECT_TRUE(mainIsGuarded(AST));
|
|
|
|
TU.Code = R"cpp(
|
|
#include "self.h" // error-ok
|
|
;
|
|
#pragma once
|
|
)cpp";
|
|
AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(),
|
|
ElementsAre(diag("recursively when building a preamble")));
|
|
EXPECT_TRUE(mainIsGuarded(AST));
|
|
}
|
|
|
|
// Tests how we handle common idioms for splitting a header-only library
|
|
// into interface and implementation files (e.g. *.h vs *.inl).
|
|
// These files mutually include each other, and need careful handling of include
|
|
// guards (which interact with preambles).
|
|
TEST(ParsedASTTest, HeaderGuardsImplIface) {
|
|
std::string Interface = R"cpp(
|
|
// error-ok: we assert on diagnostics explicitly
|
|
template <class T> struct Traits {
|
|
unsigned size();
|
|
};
|
|
#include "impl.h"
|
|
)cpp";
|
|
std::string Implementation = R"cpp(
|
|
// error-ok: we assert on diagnostics explicitly
|
|
#include "iface.h"
|
|
template <class T> unsigned Traits<T>::size() {
|
|
return sizeof(T);
|
|
}
|
|
)cpp";
|
|
|
|
TestTU TU;
|
|
TU.ImplicitHeaderGuard = false; // We're testing include guard handling!
|
|
TU.ExtraArgs.push_back("-xc++-header");
|
|
|
|
// Editing the interface file, which is include guarded (easy case).
|
|
// We mostly get this right via PP if we don't recognize the include guard.
|
|
TU.Filename = "iface.h";
|
|
TU.Code = guard(Interface);
|
|
TU.AdditionalFiles = {{"impl.h", Implementation}};
|
|
auto AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(), IsEmpty());
|
|
EXPECT_TRUE(mainIsGuarded(AST));
|
|
// Slightly harder: the `#pragma once` is part of the preamble, and we
|
|
// need to transfer it to the main file's HeaderFileInfo.
|
|
TU.Code = once(Interface);
|
|
AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(), IsEmpty());
|
|
EXPECT_TRUE(mainIsGuarded(AST));
|
|
|
|
// Editing the implementation file, which is not include guarded.
|
|
TU.Filename = "impl.h";
|
|
TU.Code = Implementation;
|
|
TU.AdditionalFiles = {{"iface.h", guard(Interface)}};
|
|
AST = TU.build();
|
|
// The diagnostic is unfortunate in this case, but correct per our model.
|
|
// Ultimately the include is skipped and the code is parsed correctly though.
|
|
EXPECT_THAT(AST.getDiagnostics(),
|
|
ElementsAre(diag("in included file: main file cannot be included "
|
|
"recursively when building a preamble")));
|
|
EXPECT_FALSE(mainIsGuarded(AST));
|
|
// Interface is pragma once guarded, same thing.
|
|
TU.AdditionalFiles = {{"iface.h", once(Interface)}};
|
|
AST = TU.build();
|
|
EXPECT_THAT(AST.getDiagnostics(),
|
|
ElementsAre(diag("in included file: main file cannot be included "
|
|
"recursively when building a preamble")));
|
|
EXPECT_FALSE(mainIsGuarded(AST));
|
|
}
|
|
|
|
TEST(ParsedASTTest, DiscoversPragmaMarks) {
|
|
TestTU TU;
|
|
TU.AdditionalFiles["Header.h"] = R"(
|
|
#pragma mark - Something API
|
|
int something();
|
|
#pragma mark Something else
|
|
)";
|
|
TU.Code = R"cpp(
|
|
#include "Header.h"
|
|
#pragma mark In Preamble
|
|
#pragma mark - Something Impl
|
|
int something() { return 1; }
|
|
#pragma mark End
|
|
)cpp";
|
|
auto AST = TU.build();
|
|
|
|
EXPECT_THAT(AST.getMarks(), ElementsAre(pragmaTrivia(" In Preamble"),
|
|
pragmaTrivia(" - Something Impl"),
|
|
pragmaTrivia(" End")));
|
|
}
|
|
|
|
TEST(ParsedASTTest, GracefulFailureOnAssemblyFile) {
|
|
std::string Filename = "TestTU.S";
|
|
std::string Code = R"S(
|
|
main:
|
|
# test comment
|
|
bx lr
|
|
)S";
|
|
|
|
// The rest is a simplified version of TestTU::build().
|
|
// Don't call TestTU::build() itself because it would assert on
|
|
// failure to build an AST.
|
|
MockFS FS;
|
|
std::string FullFilename = testPath(Filename);
|
|
FS.Files[FullFilename] = Code;
|
|
ParseInputs Inputs;
|
|
auto &Argv = Inputs.CompileCommand.CommandLine;
|
|
Argv = {"clang"};
|
|
Argv.push_back(FullFilename);
|
|
Inputs.CompileCommand.Filename = FullFilename;
|
|
Inputs.CompileCommand.Directory = testRoot();
|
|
Inputs.Contents = Code;
|
|
Inputs.TFS = &FS;
|
|
StoreDiags Diags;
|
|
auto CI = buildCompilerInvocation(Inputs, Diags);
|
|
assert(CI && "Failed to build compilation invocation.");
|
|
auto AST = ParsedAST::build(FullFilename, Inputs, std::move(CI), {}, nullptr);
|
|
|
|
EXPECT_FALSE(AST.has_value())
|
|
<< "Should not try to build AST for assembly source file";
|
|
}
|
|
|
|
TEST(ParsedASTTest, PreambleWithDifferentTarget) {
|
|
constexpr std::string_view kPreambleTarget = "x86_64";
|
|
// Specifically picking __builtin_va_list as it triggers crashes when
|
|
// switching to wasm.
|
|
// It's due to different predefined types in different targets.
|
|
auto TU = TestTU::withHeaderCode("void foo(__builtin_va_list);");
|
|
TU.Code = "void bar() { foo(2); }";
|
|
TU.ExtraArgs.emplace_back("-target");
|
|
TU.ExtraArgs.emplace_back(kPreambleTarget);
|
|
const auto Preamble = TU.preamble();
|
|
|
|
// Switch target to wasm.
|
|
TU.ExtraArgs.pop_back();
|
|
TU.ExtraArgs.emplace_back("wasm32");
|
|
|
|
IgnoreDiagnostics Diags;
|
|
MockFS FS;
|
|
auto Inputs = TU.inputs(FS);
|
|
auto CI = buildCompilerInvocation(Inputs, Diags);
|
|
ASSERT_TRUE(CI) << "Failed to build compiler invocation";
|
|
|
|
auto AST = ParsedAST::build(testPath(TU.Filename), std::move(Inputs),
|
|
std::move(CI), {}, Preamble);
|
|
|
|
ASSERT_TRUE(AST);
|
|
// We use the target from preamble, not with the most-recent flags.
|
|
EXPECT_EQ(AST->getASTContext().getTargetInfo().getTriple().getArchName(),
|
|
llvm::StringRef(kPreambleTarget));
|
|
}
|
|
} // namespace
|
|
} // namespace clangd
|
|
} // namespace clang
|