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 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 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 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 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 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