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