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