1 //===- TreeTestBase.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 // This file provides the test infrastructure for syntax trees. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "TreeTestBase.h" 14 #include "clang/AST/ASTConsumer.h" 15 #include "clang/Basic/LLVM.h" 16 #include "clang/Frontend/CompilerInstance.h" 17 #include "clang/Frontend/CompilerInvocation.h" 18 #include "clang/Frontend/FrontendAction.h" 19 #include "clang/Frontend/TextDiagnosticPrinter.h" 20 #include "clang/Lex/PreprocessorOptions.h" 21 #include "clang/Testing/CommandLineArgs.h" 22 #include "clang/Testing/TestClangConfig.h" 23 #include "clang/Tooling/Syntax/BuildTree.h" 24 #include "clang/Tooling/Syntax/Nodes.h" 25 #include "clang/Tooling/Syntax/Tokens.h" 26 #include "clang/Tooling/Syntax/Tree.h" 27 #include "llvm/ADT/ArrayRef.h" 28 #include "llvm/ADT/StringRef.h" 29 #include "llvm/Support/Casting.h" 30 #include "llvm/Support/Error.h" 31 #include "llvm/Testing/Support/Annotations.h" 32 #include "gtest/gtest.h" 33 34 using namespace clang; 35 using namespace clang::syntax; 36 37 namespace { 38 ArrayRef<syntax::Token> tokens(syntax::Node *N) { 39 assert(N->isOriginal() && "tokens of modified nodes are not well-defined"); 40 if (auto *L = dyn_cast<syntax::Leaf>(N)) 41 return llvm::makeArrayRef(L->token(), 1); 42 auto *T = cast<syntax::Tree>(N); 43 return llvm::makeArrayRef(T->firstLeaf()->token(), 44 T->lastLeaf()->token() + 1); 45 } 46 47 std::vector<TestClangConfig> allTestClangConfigs() { 48 std::vector<TestClangConfig> all_configs; 49 for (TestLanguage lang : {Lang_C89, Lang_C99, Lang_CXX03, Lang_CXX11, 50 Lang_CXX14, Lang_CXX17, Lang_CXX20}) { 51 TestClangConfig config; 52 config.Language = lang; 53 config.Target = "x86_64-pc-linux-gnu"; 54 all_configs.push_back(config); 55 56 // Windows target is interesting to test because it enables 57 // `-fdelayed-template-parsing`. 58 config.Target = "x86_64-pc-win32-msvc"; 59 all_configs.push_back(config); 60 } 61 return all_configs; 62 } 63 64 INSTANTIATE_TEST_CASE_P(SyntaxTreeTests, SyntaxTreeTest, 65 testing::ValuesIn(allTestClangConfigs()), ); 66 } // namespace 67 68 syntax::TranslationUnit * 69 SyntaxTreeTest::buildTree(StringRef Code, const TestClangConfig &ClangConfig) { 70 // FIXME: this code is almost the identical to the one in TokensTest. Share 71 // it. 72 class BuildSyntaxTree : public ASTConsumer { 73 public: 74 BuildSyntaxTree(syntax::TranslationUnit *&Root, 75 std::unique_ptr<syntax::TokenBuffer> &TB, 76 std::unique_ptr<syntax::Arena> &Arena, 77 std::unique_ptr<syntax::TokenCollector> Tokens) 78 : Root(Root), TB(TB), Arena(Arena), Tokens(std::move(Tokens)) { 79 assert(this->Tokens); 80 } 81 82 void HandleTranslationUnit(ASTContext &Ctx) override { 83 TB = std::make_unique<syntax::TokenBuffer>(std::move(*Tokens).consume()); 84 Tokens = nullptr; // make sure we fail if this gets called twice. 85 Arena = std::make_unique<syntax::Arena>(Ctx.getSourceManager(), 86 Ctx.getLangOpts(), *TB); 87 Root = syntax::buildSyntaxTree(*Arena, *Ctx.getTranslationUnitDecl()); 88 } 89 90 private: 91 syntax::TranslationUnit *&Root; 92 std::unique_ptr<syntax::TokenBuffer> &TB; 93 std::unique_ptr<syntax::Arena> &Arena; 94 std::unique_ptr<syntax::TokenCollector> Tokens; 95 }; 96 97 class BuildSyntaxTreeAction : public ASTFrontendAction { 98 public: 99 BuildSyntaxTreeAction(syntax::TranslationUnit *&Root, 100 std::unique_ptr<syntax::TokenBuffer> &TB, 101 std::unique_ptr<syntax::Arena> &Arena) 102 : Root(Root), TB(TB), Arena(Arena) {} 103 104 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, 105 StringRef InFile) override { 106 // We start recording the tokens, ast consumer will take on the result. 107 auto Tokens = 108 std::make_unique<syntax::TokenCollector>(CI.getPreprocessor()); 109 return std::make_unique<BuildSyntaxTree>(Root, TB, Arena, 110 std::move(Tokens)); 111 } 112 113 private: 114 syntax::TranslationUnit *&Root; 115 std::unique_ptr<syntax::TokenBuffer> &TB; 116 std::unique_ptr<syntax::Arena> &Arena; 117 }; 118 119 constexpr const char *FileName = "./input.cpp"; 120 FS->addFile(FileName, time_t(), llvm::MemoryBuffer::getMemBufferCopy("")); 121 122 if (!Diags->getClient()) 123 Diags->setClient(new TextDiagnosticPrinter(llvm::errs(), DiagOpts.get())); 124 Diags->setSeverityForGroup(diag::Flavor::WarningOrError, "unused-value", 125 diag::Severity::Ignored, SourceLocation()); 126 127 // Prepare to run a compiler. 128 std::vector<std::string> Args = { 129 "syntax-test", 130 "-fsyntax-only", 131 }; 132 llvm::copy(ClangConfig.getCommandLineArgs(), std::back_inserter(Args)); 133 Args.push_back(FileName); 134 135 std::vector<const char *> ArgsCStr; 136 for (const std::string &arg : Args) { 137 ArgsCStr.push_back(arg.c_str()); 138 } 139 140 Invocation = createInvocationFromCommandLine(ArgsCStr, Diags, FS); 141 assert(Invocation); 142 Invocation->getFrontendOpts().DisableFree = false; 143 Invocation->getPreprocessorOpts().addRemappedFile( 144 FileName, llvm::MemoryBuffer::getMemBufferCopy(Code).release()); 145 CompilerInstance Compiler; 146 Compiler.setInvocation(Invocation); 147 Compiler.setDiagnostics(Diags.get()); 148 Compiler.setFileManager(FileMgr.get()); 149 Compiler.setSourceManager(SourceMgr.get()); 150 151 syntax::TranslationUnit *Root = nullptr; 152 BuildSyntaxTreeAction Recorder(Root, this->TB, this->Arena); 153 154 // Action could not be executed but the frontend didn't identify any errors 155 // in the code ==> problem in setting up the action. 156 if (!Compiler.ExecuteAction(Recorder) && 157 Diags->getClient()->getNumErrors() == 0) { 158 ADD_FAILURE() << "failed to run the frontend"; 159 std::abort(); 160 } 161 return Root; 162 } 163 164 ::testing::AssertionResult SyntaxTreeTest::treeDumpEqual(StringRef Code, 165 StringRef Tree) { 166 SCOPED_TRACE(llvm::join(GetParam().getCommandLineArgs(), " ")); 167 168 auto *Root = buildTree(Code, GetParam()); 169 if (Diags->getClient()->getNumErrors() != 0) { 170 return ::testing::AssertionFailure() 171 << "Source file has syntax errors, they were printed to the test " 172 "log"; 173 } 174 std::string Actual = std::string(StringRef(Root->dump(*Arena)).trim()); 175 // EXPECT_EQ shows the diff between the two strings if they are different. 176 EXPECT_EQ(Tree.trim().str(), Actual); 177 if (Actual != Tree.trim().str()) { 178 return ::testing::AssertionFailure(); 179 } 180 return ::testing::AssertionSuccess(); 181 } 182 183 syntax::Node *SyntaxTreeTest::nodeByRange(llvm::Annotations::Range R, 184 syntax::Node *Root) { 185 ArrayRef<syntax::Token> Toks = tokens(Root); 186 187 if (Toks.front().location().isFileID() && Toks.back().location().isFileID() && 188 syntax::Token::range(*SourceMgr, Toks.front(), Toks.back()) == 189 syntax::FileRange(SourceMgr->getMainFileID(), R.Begin, R.End)) 190 return Root; 191 192 auto *T = dyn_cast<syntax::Tree>(Root); 193 if (!T) 194 return nullptr; 195 for (auto *C = T->firstChild(); C != nullptr; C = C->nextSibling()) { 196 if (auto *Result = nodeByRange(R, C)) 197 return Result; 198 } 199 return nullptr; 200 } 201