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