llvm-project
357 строк · 9.7 Кб
1//===--- DirectiveTreeTest.cpp --------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "clang-pseudo/DirectiveTree.h"
10
11#include "clang-pseudo/Token.h"
12#include "clang/Basic/LangOptions.h"
13#include "clang/Basic/TokenKinds.h"
14#include "llvm/ADT/StringExtras.h"
15#include "llvm/ADT/StringRef.h"
16#include "gmock/gmock.h"
17#include "gtest/gtest.h"
18
19namespace clang {
20namespace pseudo {
21namespace {
22
23using testing::_;
24using testing::ElementsAre;
25using testing::Matcher;
26using testing::Pair;
27using testing::StrEq;
28using Chunk = DirectiveTree::Chunk;
29
30// Matches text of a list of tokens against a string (joined with spaces).
31// e.g. EXPECT_THAT(Stream.tokens(), tokens("int main ( ) { }"));
32MATCHER_P(tokens, Tokens, "") {
33std::vector<llvm::StringRef> Texts;
34for (const Token &Tok : arg)
35Texts.push_back(Tok.text());
36return Matcher<std::string>(StrEq(Tokens))
37.MatchAndExplain(llvm::join(Texts, " "), result_listener);
38}
39
40// Matches tokens covered a directive chunk (with a Tokens property) against a
41// string, similar to tokens() above.
42// e.g. EXPECT_THAT(SomeDirective, tokensAre(Stream, "# include < vector >"));
43MATCHER_P2(tokensAre, TS, Tokens, "tokens are " + std::string(Tokens)) {
44return testing::Matches(tokens(Tokens))(TS.tokens(arg.Tokens));
45}
46
47MATCHER(directiveChunk, "") {
48return std::holds_alternative<DirectiveTree::Directive>(arg);
49}
50MATCHER(codeChunk, "") {
51return std::holds_alternative<DirectiveTree::Code>(arg);
52}
53MATCHER(conditionalChunk, "") {
54return std::holds_alternative<DirectiveTree::Conditional>(arg);
55}
56
57TEST(DirectiveTree, Parse) {
58LangOptions Opts;
59std::string Code = R"cpp(
60#include <foo.h>
61
62int main() {
63#ifdef HAS_FOO
64#if HAS_BAR
65foo(bar);
66#else
67foo(0)
68#endif
69#elif NEEDS_FOO
70#error missing_foo
71#endif
72}
73)cpp";
74
75TokenStream S = cook(lex(Code, Opts), Opts);
76DirectiveTree PP = DirectiveTree::parse(S);
77ASSERT_THAT(PP.Chunks, ElementsAre(directiveChunk(), codeChunk(),
78conditionalChunk(), codeChunk()));
79
80EXPECT_THAT(std::get<DirectiveTree::Directive>(PP.Chunks[0]),
81tokensAre(S, "# include < foo . h >"));
82EXPECT_THAT(std::get<DirectiveTree::Code>(PP.Chunks[1]),
83tokensAre(S, "int main ( ) {"));
84EXPECT_THAT(std::get<DirectiveTree::Code>(PP.Chunks[3]), tokensAre(S, "}"));
85
86const auto &Ifdef = std::get<DirectiveTree::Conditional>(PP.Chunks[2]);
87EXPECT_THAT(Ifdef.Branches,
88ElementsAre(Pair(tokensAre(S, "# ifdef HAS_FOO"), _),
89Pair(tokensAre(S, "# elif NEEDS_FOO"), _)));
90EXPECT_THAT(Ifdef.End, tokensAre(S, "# endif"));
91
92const DirectiveTree &HasFoo(Ifdef.Branches[0].second);
93const DirectiveTree &NeedsFoo(Ifdef.Branches[1].second);
94
95EXPECT_THAT(HasFoo.Chunks, ElementsAre(conditionalChunk()));
96const auto &If = std::get<DirectiveTree::Conditional>(HasFoo.Chunks[0]);
97EXPECT_THAT(If.Branches, ElementsAre(Pair(tokensAre(S, "# if HAS_BAR"), _),
98Pair(tokensAre(S, "# else"), _)));
99EXPECT_THAT(If.Branches[0].second.Chunks, ElementsAre(codeChunk()));
100EXPECT_THAT(If.Branches[1].second.Chunks, ElementsAre(codeChunk()));
101
102EXPECT_THAT(NeedsFoo.Chunks, ElementsAre(directiveChunk()));
103const auto &Error = std::get<DirectiveTree::Directive>(NeedsFoo.Chunks[0]);
104EXPECT_THAT(Error, tokensAre(S, "# error missing_foo"));
105EXPECT_EQ(Error.Kind, tok::pp_error);
106}
107
108TEST(DirectiveTree, ParseUgly) {
109LangOptions Opts;
110std::string Code = R"cpp(
111/*A*/ # /*B*/ \
112/*C*/ \
113define \
114BAR /*D*/
115/*E*/
116)cpp";
117TokenStream S = cook(lex(Code, Opts), Opts);
118DirectiveTree PP = DirectiveTree::parse(S);
119
120ASSERT_THAT(PP.Chunks,
121ElementsAre(codeChunk(), directiveChunk(), codeChunk()));
122EXPECT_THAT(std::get<DirectiveTree::Code>(PP.Chunks[0]),
123tokensAre(S, "/*A*/"));
124const auto &Define = std::get<DirectiveTree::Directive>(PP.Chunks[1]);
125EXPECT_EQ(Define.Kind, tok::pp_define);
126EXPECT_THAT(Define, tokensAre(S, "# /*B*/ /*C*/ define BAR /*D*/"));
127EXPECT_THAT(std::get<DirectiveTree::Code>(PP.Chunks[2]),
128tokensAre(S, "/*E*/"));
129}
130
131TEST(DirectiveTree, ParseBroken) {
132LangOptions Opts;
133std::string Code = R"cpp(
134a
135#endif // mismatched
136#if X
137b
138)cpp";
139TokenStream S = cook(lex(Code, Opts), Opts);
140DirectiveTree PP = DirectiveTree::parse(S);
141
142ASSERT_THAT(PP.Chunks,
143ElementsAre(codeChunk(), directiveChunk(), conditionalChunk()));
144EXPECT_THAT(std::get<DirectiveTree::Code>(PP.Chunks[0]), tokensAre(S, "a"));
145const auto &Endif = std::get<DirectiveTree::Directive>(PP.Chunks[1]);
146EXPECT_EQ(Endif.Kind, tok::pp_endif);
147EXPECT_THAT(Endif, tokensAre(S, "# endif // mismatched"));
148
149const auto &X = std::get<DirectiveTree::Conditional>(PP.Chunks[2]);
150EXPECT_EQ(1u, X.Branches.size());
151// The (only) branch of the broken conditional section runs until eof.
152EXPECT_EQ(tok::pp_if, X.Branches.front().first.Kind);
153EXPECT_THAT(X.Branches.front().second.Chunks, ElementsAre(codeChunk()));
154// The missing terminating directive is marked as pp_not_keyword.
155EXPECT_EQ(tok::pp_not_keyword, X.End.Kind);
156EXPECT_EQ(0u, X.End.Tokens.size());
157}
158
159TEST(DirectiveTree, ChooseBranches) {
160LangOptions Opts;
161const std::string Cases[] = {
162R"cpp(
163// Branches with no alternatives are taken
164#if COND // TAKEN
165int x;
166#endif
167)cpp",
168
169R"cpp(
170// Empty branches are better than nothing
171#if COND // TAKEN
172#endif
173)cpp",
174
175R"cpp(
176// Trivially false branches are not taken, even with no alternatives.
177#if 0
178int x;
179#endif
180)cpp",
181
182R"cpp(
183// Longer branches are preferred over shorter branches
184#if COND // TAKEN
185int x = 1;
186#else
187int x;
188#endif
189
190#if COND
191int x;
192#else // TAKEN
193int x = 1;
194#endif
195)cpp",
196
197R"cpp(
198// Trivially true branches are taken if previous branches are trivial.
199#if 1 // TAKEN
200#else
201int x = 1;
202#endif
203
204#if 0
205int x = 1;
206#elif 0
207int x = 2;
208#elif 1 // TAKEN
209int x;
210#endif
211
212#if 0
213int x = 1;
214#elif FOO // TAKEN
215int x = 2;
216#elif 1
217int x;
218#endif
219)cpp",
220
221R"cpp(
222// #else is a trivially true branch
223#if 0
224int x = 1;
225#elif 0
226int x = 2;
227#else // TAKEN
228int x;
229#endif
230)cpp",
231
232R"cpp(
233// Directives break ties, but nondirective text is more important.
234#if FOO
235#define A 1 2 3
236#else // TAKEN
237#define B 4 5 6
238#define C 7 8 9
239#endif
240
241#if FOO // TAKEN
242;
243#define A 1 2 3
244#else
245#define B 4 5 6
246#define C 7 8 9
247#endif
248)cpp",
249
250R"cpp(
251// Avoid #error directives.
252#if FOO
253int x = 42;
254#error This branch is no good
255#else // TAKEN
256#endif
257
258#if FOO
259// All paths here lead to errors.
260int x = 42;
261#if 1 // TAKEN
262#if COND // TAKEN
263#error This branch is no good
264#else
265#error This one is no good either
266#endif
267#endif
268#else // TAKEN
269#endif
270)cpp",
271
272R"cpp(
273// Populate taken branches recursively.
274#if FOO // TAKEN
275int x = 42;
276#if BAR
277;
278#else // TAKEN
279int y = 43;
280#endif
281#else
282int x;
283#if BAR // TAKEN
284int y;
285#else
286;
287#endif
288#endif
289)cpp",
290};
291for (const auto &Code : Cases) {
292TokenStream S = cook(lex(Code, Opts), Opts);
293
294std::function<void(const DirectiveTree &)> Verify =
295[&](const DirectiveTree &M) {
296for (const auto &C : M.Chunks) {
297if (!std::holds_alternative<DirectiveTree::Conditional>(C))
298continue;
299const DirectiveTree::Conditional &Cond =
300std::get<DirectiveTree::Conditional>(C);
301for (unsigned I = 0; I < Cond.Branches.size(); ++I) {
302auto Directive = S.tokens(Cond.Branches[I].first.Tokens);
303EXPECT_EQ(I == Cond.Taken, Directive.back().text() == "// TAKEN")
304<< "At line " << Directive.front().Line << " of: " << Code;
305Verify(Cond.Branches[I].second);
306}
307}
308};
309
310DirectiveTree Tree = DirectiveTree::parse(S);
311chooseConditionalBranches(Tree, S);
312Verify(Tree);
313}
314}
315
316TEST(DirectiveTree, StripDirectives) {
317LangOptions Opts;
318std::string Code = R"cpp(
319#include <stddef.h>
320a a a
321#warning AAA
322b b b
323#if 1
324c c c
325#warning BBB
326#if 0
327d d d
328#warning CC
329#else
330e e e
331#endif
332f f f
333#if 0
334g g g
335#endif
336h h h
337#else
338i i i
339#endif
340j j j
341)cpp";
342TokenStream S = lex(Code, Opts);
343
344DirectiveTree Tree = DirectiveTree::parse(S);
345chooseConditionalBranches(Tree, S);
346EXPECT_THAT(Tree.stripDirectives(S).tokens(),
347tokens("a a a b b b c c c e e e f f f h h h j j j"));
348
349const DirectiveTree &Part =
350std::get<DirectiveTree::Conditional>(Tree.Chunks[4]).Branches[0].second;
351EXPECT_THAT(Part.stripDirectives(S).tokens(),
352tokens("c c c e e e f f f h h h"));
353}
354
355} // namespace
356} // namespace pseudo
357} // namespace clang
358