1 //===- TreeTest.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/Tooling/Syntax/Tree.h" 10 #include "clang/AST/ASTConsumer.h" 11 #include "clang/AST/Decl.h" 12 #include "clang/Frontend/CompilerInstance.h" 13 #include "clang/Frontend/FrontendAction.h" 14 #include "clang/Lex/PreprocessorOptions.h" 15 #include "clang/Tooling/Syntax/BuildTree.h" 16 #include "clang/Tooling/Syntax/Nodes.h" 17 #include "clang/Tooling/Tooling.h" 18 #include "llvm/ADT/STLExtras.h" 19 #include "llvm/ADT/StringRef.h" 20 #include "gmock/gmock.h" 21 #include "gtest/gtest.h" 22 #include <cstdlib> 23 24 using namespace clang; 25 26 namespace { 27 class SyntaxTreeTest : public ::testing::Test { 28 protected: 29 // Build a syntax tree for the code. 30 syntax::TranslationUnit *buildTree(llvm::StringRef Code) { 31 // FIXME: this code is almost the identical to the one in TokensTest. Share 32 // it. 33 class BuildSyntaxTree : public ASTConsumer { 34 public: 35 BuildSyntaxTree(syntax::TranslationUnit *&Root, 36 std::unique_ptr<syntax::Arena> &Arena, 37 std::unique_ptr<syntax::TokenCollector> Tokens) 38 : Root(Root), Arena(Arena), Tokens(std::move(Tokens)) { 39 assert(this->Tokens); 40 } 41 42 void HandleTranslationUnit(ASTContext &Ctx) override { 43 Arena = std::make_unique<syntax::Arena>(Ctx.getSourceManager(), 44 Ctx.getLangOpts(), 45 std::move(*Tokens).consume()); 46 Tokens = nullptr; // make sure we fail if this gets called twice. 47 Root = syntax::buildSyntaxTree(*Arena, *Ctx.getTranslationUnitDecl()); 48 } 49 50 private: 51 syntax::TranslationUnit *&Root; 52 std::unique_ptr<syntax::Arena> &Arena; 53 std::unique_ptr<syntax::TokenCollector> Tokens; 54 }; 55 56 class BuildSyntaxTreeAction : public ASTFrontendAction { 57 public: 58 BuildSyntaxTreeAction(syntax::TranslationUnit *&Root, 59 std::unique_ptr<syntax::Arena> &Arena) 60 : Root(Root), Arena(Arena) {} 61 62 std::unique_ptr<ASTConsumer> 63 CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { 64 // We start recording the tokens, ast consumer will take on the result. 65 auto Tokens = 66 std::make_unique<syntax::TokenCollector>(CI.getPreprocessor()); 67 return std::make_unique<BuildSyntaxTree>(Root, Arena, 68 std::move(Tokens)); 69 } 70 71 private: 72 syntax::TranslationUnit *&Root; 73 std::unique_ptr<syntax::Arena> &Arena; 74 }; 75 76 constexpr const char *FileName = "./input.cpp"; 77 FS->addFile(FileName, time_t(), llvm::MemoryBuffer::getMemBufferCopy("")); 78 if (!Diags->getClient()) 79 Diags->setClient(new IgnoringDiagConsumer); 80 // Prepare to run a compiler. 81 std::vector<const char *> Args = {"syntax-test", "-std=c++11", 82 "-fsyntax-only", FileName}; 83 auto CI = createInvocationFromCommandLine(Args, Diags, FS); 84 assert(CI); 85 CI->getFrontendOpts().DisableFree = false; 86 CI->getPreprocessorOpts().addRemappedFile( 87 FileName, llvm::MemoryBuffer::getMemBufferCopy(Code).release()); 88 CompilerInstance Compiler; 89 Compiler.setInvocation(std::move(CI)); 90 Compiler.setDiagnostics(Diags.get()); 91 Compiler.setFileManager(FileMgr.get()); 92 Compiler.setSourceManager(SourceMgr.get()); 93 94 syntax::TranslationUnit *Root = nullptr; 95 BuildSyntaxTreeAction Recorder(Root, this->Arena); 96 if (!Compiler.ExecuteAction(Recorder)) { 97 ADD_FAILURE() << "failed to run the frontend"; 98 std::abort(); 99 } 100 return Root; 101 } 102 103 // Adds a file to the test VFS. 104 void addFile(llvm::StringRef Path, llvm::StringRef Contents) { 105 if (!FS->addFile(Path, time_t(), 106 llvm::MemoryBuffer::getMemBufferCopy(Contents))) { 107 ADD_FAILURE() << "could not add a file to VFS: " << Path; 108 } 109 } 110 111 // Data fields. 112 llvm::IntrusiveRefCntPtr<DiagnosticsEngine> Diags = 113 new DiagnosticsEngine(new DiagnosticIDs, new DiagnosticOptions); 114 IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> FS = 115 new llvm::vfs::InMemoryFileSystem; 116 llvm::IntrusiveRefCntPtr<FileManager> FileMgr = 117 new FileManager(FileSystemOptions(), FS); 118 llvm::IntrusiveRefCntPtr<SourceManager> SourceMgr = 119 new SourceManager(*Diags, *FileMgr); 120 // Set after calling buildTree(). 121 std::unique_ptr<syntax::Arena> Arena; 122 }; 123 124 TEST_F(SyntaxTreeTest, Basic) { 125 std::pair</*Input*/ std::string, /*Expected*/ std::string> Cases[] = { 126 { 127 R"cpp( 128 int main() {} 129 void foo() {} 130 )cpp", 131 R"txt( 132 *: TranslationUnit 133 |-TopLevelDeclaration 134 | |-int 135 | |-main 136 | |-( 137 | |-) 138 | `-CompoundStatement 139 | |-{ 140 | `-} 141 `-TopLevelDeclaration 142 |-void 143 |-foo 144 |-( 145 |-) 146 `-CompoundStatement 147 |-{ 148 `-} 149 )txt"}, 150 // if. 151 { 152 R"cpp( 153 int main() { 154 if (true) {} 155 if (true) {} else if (false) {} 156 } 157 )cpp", 158 R"txt( 159 *: TranslationUnit 160 `-TopLevelDeclaration 161 |-int 162 |-main 163 |-( 164 |-) 165 `-CompoundStatement 166 |-{ 167 |-IfStatement 168 | |-if 169 | |-( 170 | |-UnknownExpression 171 | | `-true 172 | |-) 173 | `-CompoundStatement 174 | |-{ 175 | `-} 176 |-IfStatement 177 | |-if 178 | |-( 179 | |-UnknownExpression 180 | | `-true 181 | |-) 182 | |-CompoundStatement 183 | | |-{ 184 | | `-} 185 | |-else 186 | `-IfStatement 187 | |-if 188 | |-( 189 | |-UnknownExpression 190 | | `-false 191 | |-) 192 | `-CompoundStatement 193 | |-{ 194 | `-} 195 `-} 196 )txt"}, 197 // for. 198 {R"cpp( 199 void test() { 200 for (;;) {} 201 } 202 )cpp", 203 R"txt( 204 *: TranslationUnit 205 `-TopLevelDeclaration 206 |-void 207 |-test 208 |-( 209 |-) 210 `-CompoundStatement 211 |-{ 212 |-ForStatement 213 | |-for 214 | |-( 215 | |-; 216 | |-; 217 | |-) 218 | `-CompoundStatement 219 | |-{ 220 | `-} 221 `-} 222 )txt"}, 223 // declaration statement. 224 {"void test() { int a = 10; }", 225 R"txt( 226 *: TranslationUnit 227 `-TopLevelDeclaration 228 |-void 229 |-test 230 |-( 231 |-) 232 `-CompoundStatement 233 |-{ 234 |-DeclarationStatement 235 | |-int 236 | |-a 237 | |-= 238 | |-10 239 | `-; 240 `-} 241 )txt"}, 242 {"void test() { ; }", R"txt( 243 *: TranslationUnit 244 `-TopLevelDeclaration 245 |-void 246 |-test 247 |-( 248 |-) 249 `-CompoundStatement 250 |-{ 251 |-EmptyStatement 252 | `-; 253 `-} 254 )txt"}, 255 // switch, case and default. 256 {R"cpp( 257 void test() { 258 switch (true) { 259 case 0: 260 default:; 261 } 262 } 263 )cpp", 264 R"txt( 265 *: TranslationUnit 266 `-TopLevelDeclaration 267 |-void 268 |-test 269 |-( 270 |-) 271 `-CompoundStatement 272 |-{ 273 |-SwitchStatement 274 | |-switch 275 | |-( 276 | |-UnknownExpression 277 | | `-true 278 | |-) 279 | `-CompoundStatement 280 | |-{ 281 | |-CaseStatement 282 | | |-case 283 | | |-UnknownExpression 284 | | | `-0 285 | | |-: 286 | | `-DefaultStatement 287 | | |-default 288 | | |-: 289 | | `-EmptyStatement 290 | | `-; 291 | `-} 292 `-} 293 )txt"}, 294 // while. 295 {R"cpp( 296 void test() { 297 while (true) { continue; break; } 298 } 299 )cpp", 300 R"txt( 301 *: TranslationUnit 302 `-TopLevelDeclaration 303 |-void 304 |-test 305 |-( 306 |-) 307 `-CompoundStatement 308 |-{ 309 |-WhileStatement 310 | |-while 311 | |-( 312 | |-UnknownExpression 313 | | `-true 314 | |-) 315 | `-CompoundStatement 316 | |-{ 317 | |-ContinueStatement 318 | | |-continue 319 | | `-; 320 | |-BreakStatement 321 | | |-break 322 | | `-; 323 | `-} 324 `-} 325 )txt"}, 326 // return. 327 {R"cpp( 328 int test() { return 1; } 329 )cpp", 330 R"txt( 331 *: TranslationUnit 332 `-TopLevelDeclaration 333 |-int 334 |-test 335 |-( 336 |-) 337 `-CompoundStatement 338 |-{ 339 |-ReturnStatement 340 | |-return 341 | |-UnknownExpression 342 | | `-1 343 | `-; 344 `-} 345 )txt"}, 346 // Range-based for. 347 {R"cpp( 348 void test() { 349 int a[3]; 350 for (int x : a) ; 351 } 352 )cpp", 353 R"txt( 354 *: TranslationUnit 355 `-TopLevelDeclaration 356 |-void 357 |-test 358 |-( 359 |-) 360 `-CompoundStatement 361 |-{ 362 |-DeclarationStatement 363 | |-int 364 | |-a 365 | |-[ 366 | |-3 367 | |-] 368 | `-; 369 |-RangeBasedForStatement 370 | |-for 371 | |-( 372 | |-int 373 | |-x 374 | |-: 375 | |-UnknownExpression 376 | | `-a 377 | |-) 378 | `-EmptyStatement 379 | `-; 380 `-} 381 )txt"}, 382 // Unhandled statements should end up as 'unknown statement'. 383 // This example uses a 'label statement', which does not yet have a syntax 384 // counterpart. 385 {"void main() { foo: return 100; }", R"txt( 386 *: TranslationUnit 387 `-TopLevelDeclaration 388 |-void 389 |-main 390 |-( 391 |-) 392 `-CompoundStatement 393 |-{ 394 |-UnknownStatement 395 | |-foo 396 | |-: 397 | `-ReturnStatement 398 | |-return 399 | |-UnknownExpression 400 | | `-100 401 | `-; 402 `-} 403 )txt"}, 404 // expressions should be wrapped in 'ExpressionStatement' when they appear 405 // in a statement position. 406 {R"cpp( 407 void test() { 408 test(); 409 if (true) test(); else test(); 410 } 411 )cpp", 412 R"txt( 413 *: TranslationUnit 414 `-TopLevelDeclaration 415 |-void 416 |-test 417 |-( 418 |-) 419 `-CompoundStatement 420 |-{ 421 |-ExpressionStatement 422 | |-UnknownExpression 423 | | |-test 424 | | |-( 425 | | `-) 426 | `-; 427 |-IfStatement 428 | |-if 429 | |-( 430 | |-UnknownExpression 431 | | `-true 432 | |-) 433 | |-ExpressionStatement 434 | | |-UnknownExpression 435 | | | |-test 436 | | | |-( 437 | | | `-) 438 | | `-; 439 | |-else 440 | `-ExpressionStatement 441 | |-UnknownExpression 442 | | |-test 443 | | |-( 444 | | `-) 445 | `-; 446 `-} 447 )txt"}}; 448 449 for (const auto &T : Cases) { 450 auto *Root = buildTree(T.first); 451 std::string Expected = llvm::StringRef(T.second).trim().str(); 452 std::string Actual = llvm::StringRef(Root->dump(*Arena)).trim(); 453 EXPECT_EQ(Expected, Actual) << "the resulting dump is:\n" << Actual; 454 } 455 } 456 } // namespace 457