forked from OSchip/llvm-project
307 lines
8.5 KiB
C++
307 lines
8.5 KiB
C++
//===--- DirectiveTreeTest.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 "clang-pseudo/DirectiveTree.h"
|
|
|
|
#include "clang-pseudo/Token.h"
|
|
#include "clang/Basic/LangOptions.h"
|
|
#include "clang/Basic/TokenKinds.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
namespace clang {
|
|
namespace pseudo {
|
|
namespace {
|
|
|
|
using testing::_;
|
|
using testing::ElementsAre;
|
|
using testing::Matcher;
|
|
using testing::Pair;
|
|
using testing::StrEq;
|
|
using Chunk = DirectiveTree::Chunk;
|
|
|
|
MATCHER_P2(tokensAre, TS, Tokens, "tokens are " + std::string(Tokens)) {
|
|
std::vector<llvm::StringRef> Texts;
|
|
for (const Token &Tok : TS.tokens(arg.Tokens))
|
|
Texts.push_back(Tok.text());
|
|
return Matcher<std::string>(StrEq(Tokens))
|
|
.MatchAndExplain(llvm::join(Texts, " "), result_listener);
|
|
}
|
|
|
|
MATCHER_P(chunkKind, K, "") { return arg.kind() == K; }
|
|
|
|
TEST(DirectiveTree, Parse) {
|
|
LangOptions Opts;
|
|
std::string Code = R"cpp(
|
|
#include <foo.h>
|
|
|
|
int main() {
|
|
#ifdef HAS_FOO
|
|
#if HAS_BAR
|
|
foo(bar);
|
|
#else
|
|
foo(0)
|
|
#endif
|
|
#elif NEEDS_FOO
|
|
#error missing_foo
|
|
#endif
|
|
}
|
|
)cpp";
|
|
|
|
TokenStream S = cook(lex(Code, Opts), Opts);
|
|
DirectiveTree PP = DirectiveTree::parse(S);
|
|
|
|
ASSERT_THAT(PP.Chunks, ElementsAre(chunkKind(Chunk::K_Directive),
|
|
chunkKind(Chunk::K_Code),
|
|
chunkKind(Chunk::K_Conditional),
|
|
chunkKind(Chunk::K_Code)));
|
|
|
|
EXPECT_THAT((const DirectiveTree::Directive &)PP.Chunks[0],
|
|
tokensAre(S, "# include < foo . h >"));
|
|
EXPECT_THAT((const DirectiveTree::Code &)PP.Chunks[1],
|
|
tokensAre(S, "int main ( ) {"));
|
|
EXPECT_THAT((const DirectiveTree::Code &)PP.Chunks[3], tokensAre(S, "}"));
|
|
|
|
const DirectiveTree::Conditional &Ifdef(PP.Chunks[2]);
|
|
EXPECT_THAT(Ifdef.Branches,
|
|
ElementsAre(Pair(tokensAre(S, "# ifdef HAS_FOO"), _),
|
|
Pair(tokensAre(S, "# elif NEEDS_FOO"), _)));
|
|
EXPECT_THAT(Ifdef.End, tokensAre(S, "# endif"));
|
|
|
|
const DirectiveTree &HasFoo(Ifdef.Branches[0].second);
|
|
const DirectiveTree &NeedsFoo(Ifdef.Branches[1].second);
|
|
|
|
EXPECT_THAT(HasFoo.Chunks, ElementsAre(chunkKind(Chunk::K_Conditional)));
|
|
const DirectiveTree::Conditional &If(HasFoo.Chunks[0]);
|
|
EXPECT_THAT(If.Branches, ElementsAre(Pair(tokensAre(S, "# if HAS_BAR"), _),
|
|
Pair(tokensAre(S, "# else"), _)));
|
|
EXPECT_THAT(If.Branches[0].second.Chunks,
|
|
ElementsAre(chunkKind(Chunk::K_Code)));
|
|
EXPECT_THAT(If.Branches[1].second.Chunks,
|
|
ElementsAre(chunkKind(Chunk::K_Code)));
|
|
|
|
EXPECT_THAT(NeedsFoo.Chunks, ElementsAre(chunkKind(Chunk::K_Directive)));
|
|
const DirectiveTree::Directive &Error(NeedsFoo.Chunks[0]);
|
|
EXPECT_THAT(Error, tokensAre(S, "# error missing_foo"));
|
|
EXPECT_EQ(Error.Kind, tok::pp_error);
|
|
}
|
|
|
|
TEST(DirectiveTree, ParseUgly) {
|
|
LangOptions Opts;
|
|
std::string Code = R"cpp(
|
|
/*A*/ # /*B*/ \
|
|
/*C*/ \
|
|
define \
|
|
BAR /*D*/
|
|
/*E*/
|
|
)cpp";
|
|
TokenStream S = cook(lex(Code, Opts), Opts);
|
|
DirectiveTree PP = DirectiveTree::parse(S);
|
|
|
|
ASSERT_THAT(PP.Chunks, ElementsAre(chunkKind(Chunk::K_Code),
|
|
chunkKind(Chunk::K_Directive),
|
|
chunkKind(Chunk::K_Code)));
|
|
EXPECT_THAT((const DirectiveTree::Code &)PP.Chunks[0], tokensAre(S, "/*A*/"));
|
|
const DirectiveTree::Directive &Define(PP.Chunks[1]);
|
|
EXPECT_EQ(Define.Kind, tok::pp_define);
|
|
EXPECT_THAT(Define, tokensAre(S, "# /*B*/ /*C*/ define BAR /*D*/"));
|
|
EXPECT_THAT((const DirectiveTree::Code &)PP.Chunks[2], tokensAre(S, "/*E*/"));
|
|
}
|
|
|
|
TEST(DirectiveTree, ParseBroken) {
|
|
LangOptions Opts;
|
|
std::string Code = R"cpp(
|
|
a
|
|
#endif // mismatched
|
|
#if X
|
|
b
|
|
)cpp";
|
|
TokenStream S = cook(lex(Code, Opts), Opts);
|
|
DirectiveTree PP = DirectiveTree::parse(S);
|
|
|
|
ASSERT_THAT(PP.Chunks, ElementsAre(chunkKind(Chunk::K_Code),
|
|
chunkKind(Chunk::K_Directive),
|
|
chunkKind(Chunk::K_Conditional)));
|
|
EXPECT_THAT((const DirectiveTree::Code &)PP.Chunks[0], tokensAre(S, "a"));
|
|
const DirectiveTree::Directive &Endif(PP.Chunks[1]);
|
|
EXPECT_EQ(Endif.Kind, tok::pp_endif);
|
|
EXPECT_THAT(Endif, tokensAre(S, "# endif // mismatched"));
|
|
|
|
const DirectiveTree::Conditional &X(PP.Chunks[2]);
|
|
EXPECT_EQ(1u, X.Branches.size());
|
|
// The (only) branch of the broken conditional section runs until eof.
|
|
EXPECT_EQ(tok::pp_if, X.Branches.front().first.Kind);
|
|
EXPECT_THAT(X.Branches.front().second.Chunks,
|
|
ElementsAre(chunkKind(Chunk::K_Code)));
|
|
// The missing terminating directive is marked as pp_not_keyword.
|
|
EXPECT_EQ(tok::pp_not_keyword, X.End.Kind);
|
|
EXPECT_EQ(0u, X.End.Tokens.size());
|
|
}
|
|
|
|
TEST(DirectiveTree, ChooseBranches) {
|
|
LangOptions Opts;
|
|
const std::string Cases[] = {
|
|
R"cpp(
|
|
// Branches with no alternatives are taken
|
|
#if COND // TAKEN
|
|
int x;
|
|
#endif
|
|
)cpp",
|
|
|
|
R"cpp(
|
|
// Empty branches are better than nothing
|
|
#if COND // TAKEN
|
|
#endif
|
|
)cpp",
|
|
|
|
R"cpp(
|
|
// Trivially false branches are not taken, even with no alternatives.
|
|
#if 0
|
|
int x;
|
|
#endif
|
|
)cpp",
|
|
|
|
R"cpp(
|
|
// Longer branches are preferred over shorter branches
|
|
#if COND // TAKEN
|
|
int x = 1;
|
|
#else
|
|
int x;
|
|
#endif
|
|
|
|
#if COND
|
|
int x;
|
|
#else // TAKEN
|
|
int x = 1;
|
|
#endif
|
|
)cpp",
|
|
|
|
R"cpp(
|
|
// Trivially true branches are taken if previous branches are trivial.
|
|
#if 1 // TAKEN
|
|
#else
|
|
int x = 1;
|
|
#endif
|
|
|
|
#if 0
|
|
int x = 1;
|
|
#elif 0
|
|
int x = 2;
|
|
#elif 1 // TAKEN
|
|
int x;
|
|
#endif
|
|
|
|
#if 0
|
|
int x = 1;
|
|
#elif FOO // TAKEN
|
|
int x = 2;
|
|
#elif 1
|
|
int x;
|
|
#endif
|
|
)cpp",
|
|
|
|
R"cpp(
|
|
// #else is a trivially true branch
|
|
#if 0
|
|
int x = 1;
|
|
#elif 0
|
|
int x = 2;
|
|
#else // TAKEN
|
|
int x;
|
|
#endif
|
|
)cpp",
|
|
|
|
R"cpp(
|
|
// Directives break ties, but nondirective text is more important.
|
|
#if FOO
|
|
#define A 1 2 3
|
|
#else // TAKEN
|
|
#define B 4 5 6
|
|
#define C 7 8 9
|
|
#endif
|
|
|
|
#if FOO // TAKEN
|
|
;
|
|
#define A 1 2 3
|
|
#else
|
|
#define B 4 5 6
|
|
#define C 7 8 9
|
|
#endif
|
|
)cpp",
|
|
|
|
R"cpp(
|
|
// Avoid #error directives.
|
|
#if FOO
|
|
int x = 42;
|
|
#error This branch is no good
|
|
#else // TAKEN
|
|
#endif
|
|
|
|
#if FOO
|
|
// All paths here lead to errors.
|
|
int x = 42;
|
|
#if 1 // TAKEN
|
|
#if COND // TAKEN
|
|
#error This branch is no good
|
|
#else
|
|
#error This one is no good either
|
|
#endif
|
|
#endif
|
|
#else // TAKEN
|
|
#endif
|
|
)cpp",
|
|
|
|
R"cpp(
|
|
// Populate taken branches recursively.
|
|
#if FOO // TAKEN
|
|
int x = 42;
|
|
#if BAR
|
|
;
|
|
#else // TAKEN
|
|
int y = 43;
|
|
#endif
|
|
#else
|
|
int x;
|
|
#if BAR // TAKEN
|
|
int y;
|
|
#else
|
|
;
|
|
#endif
|
|
#endif
|
|
)cpp",
|
|
};
|
|
for (const auto &Code : Cases) {
|
|
TokenStream S = cook(lex(Code, Opts), Opts);
|
|
|
|
std::function<void(const DirectiveTree &)> Verify =
|
|
[&](const DirectiveTree &M) {
|
|
for (const auto &C : M.Chunks) {
|
|
if (C.kind() != DirectiveTree::Chunk::K_Conditional)
|
|
continue;
|
|
const DirectiveTree::Conditional &Cond(C);
|
|
for (unsigned I = 0; I < Cond.Branches.size(); ++I) {
|
|
auto Directive = S.tokens(Cond.Branches[I].first.Tokens);
|
|
EXPECT_EQ(I == Cond.Taken, Directive.back().text() == "// TAKEN")
|
|
<< "At line " << Directive.front().Line << " of: " << Code;
|
|
Verify(Cond.Branches[I].second);
|
|
}
|
|
}
|
|
};
|
|
|
|
DirectiveTree Tree = DirectiveTree::parse(S);
|
|
chooseConditionalBranches(Tree, S);
|
|
Verify(Tree);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace pseudo
|
|
} // namespace clang
|