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