|  | //===-- 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 |