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