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, "") {
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 >"));
43MATCHER_P2(tokensAre, TS, Tokens, "tokens are " + std::string(Tokens)) {
44 return testing::Matches(tokens(Tokens))(TS.tokens(arg.Tokens));
45}
46
47MATCHER(directiveChunk, "") {
48 return std::holds_alternative<DirectiveTree::Directive>(arg);
49}
50MATCHER(codeChunk, "") {
51 return std::holds_alternative<DirectiveTree::Code>(arg);
52}
53MATCHER(conditionalChunk, "") {
54 return std::holds_alternative<DirectiveTree::Conditional>(arg);
55}
56
57TEST(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
108TEST(DirectiveTree, ParseUgly) {
109 LangOptions Opts;
110 std::string Code = R"cpp(
111 /*A*/ # /*B*/ \
112 /*C*/ \
113define \
114BAR /*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
131TEST(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
159TEST(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
316TEST(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

source code of clang-tools-extra/pseudo/unittests/DirectiveTreeTest.cpp