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 const TokenBufferTokenManager &STM) { 40 assert(N->isOriginal() && "tokens of modified nodes are not well-defined"); 41 if (auto *L = dyn_cast<syntax::Leaf>(N)) 42 return llvm::makeArrayRef(STM.getToken(L->getTokenKey()), 1); 43 auto *T = cast<syntax::Tree>(N); 44 return llvm::makeArrayRef(STM.getToken(T->findFirstLeaf()->getTokenKey()), 45 STM.getToken(T->findLastLeaf()->getTokenKey()) + 1); 46 } 47 } // namespace 48 49 std::vector<TestClangConfig> clang::syntax::allTestClangConfigs() { 50 std::vector<TestClangConfig> all_configs; 51 for (TestLanguage lang : {Lang_C89, Lang_C99, Lang_CXX03, Lang_CXX11, 52 Lang_CXX14, Lang_CXX17, Lang_CXX20}) { 53 TestClangConfig config; 54 config.Language = lang; 55 config.Target = "x86_64-pc-linux-gnu"; 56 all_configs.push_back(config); 57 58 // Windows target is interesting to test because it enables 59 // `-fdelayed-template-parsing`. 60 config.Target = "x86_64-pc-win32-msvc"; 61 all_configs.push_back(config); 62 } 63 return all_configs; 64 } 65 66 syntax::TranslationUnit * 67 SyntaxTreeTest::buildTree(StringRef Code, const TestClangConfig &ClangConfig) { 68 // FIXME: this code is almost the identical to the one in TokensTest. Share 69 // it. 70 class BuildSyntaxTree : public ASTConsumer { 71 public: 72 BuildSyntaxTree(syntax::TranslationUnit *&Root, 73 std::unique_ptr<syntax::TokenBuffer> &TB, 74 std::unique_ptr<syntax::TokenBufferTokenManager> &TM, 75 std::unique_ptr<syntax::Arena> &Arena, 76 std::unique_ptr<syntax::TokenCollector> Tokens) 77 : Root(Root), TB(TB), TM(TM), Arena(Arena), Tokens(std::move(Tokens)) { 78 assert(this->Tokens); 79 } 80 81 void HandleTranslationUnit(ASTContext &Ctx) override { 82 TB = std::make_unique<syntax::TokenBuffer>(std::move(*Tokens).consume()); 83 Tokens = nullptr; // make sure we fail if this gets called twice. 84 TM = std::make_unique<syntax::TokenBufferTokenManager>( 85 *TB, Ctx.getLangOpts(), Ctx.getSourceManager()); 86 Arena = std::make_unique<syntax::Arena>(); 87 Root = syntax::buildSyntaxTree(*Arena, *TM, Ctx); 88 } 89 90 private: 91 syntax::TranslationUnit *&Root; 92 std::unique_ptr<syntax::TokenBuffer> &TB; 93 std::unique_ptr<syntax::TokenBufferTokenManager> &TM; 94 std::unique_ptr<syntax::Arena> &Arena; 95 std::unique_ptr<syntax::TokenCollector> Tokens; 96 }; 97 98 class BuildSyntaxTreeAction : public ASTFrontendAction { 99 public: 100 BuildSyntaxTreeAction(syntax::TranslationUnit *&Root, 101 std::unique_ptr<syntax::TokenBufferTokenManager> &TM, 102 std::unique_ptr<syntax::TokenBuffer> &TB, 103 std::unique_ptr<syntax::Arena> &Arena) 104 : Root(Root), TM(TM), TB(TB), Arena(Arena) {} 105 106 std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, 107 StringRef InFile) override { 108 // We start recording the tokens, ast consumer will take on the result. 109 auto Tokens = 110 std::make_unique<syntax::TokenCollector>(CI.getPreprocessor()); 111 return std::make_unique<BuildSyntaxTree>(Root, TB, TM, Arena, 112 std::move(Tokens)); 113 } 114 115 private: 116 syntax::TranslationUnit *&Root; 117 std::unique_ptr<syntax::TokenBufferTokenManager> &TM; 118 std::unique_ptr<syntax::TokenBuffer> &TB; 119 std::unique_ptr<syntax::Arena> &Arena; 120 }; 121 122 constexpr const char *FileName = "./input.cpp"; 123 FS->addFile(FileName, time_t(), llvm::MemoryBuffer::getMemBufferCopy("")); 124 125 if (!Diags->getClient()) 126 Diags->setClient(new TextDiagnosticPrinter(llvm::errs(), DiagOpts.get())); 127 Diags->setSeverityForGroup(diag::Flavor::WarningOrError, "unused-value", 128 diag::Severity::Ignored, SourceLocation()); 129 130 // Prepare to run a compiler. 131 std::vector<std::string> Args = { 132 "syntax-test", 133 "-fsyntax-only", 134 }; 135 llvm::copy(ClangConfig.getCommandLineArgs(), std::back_inserter(Args)); 136 Args.push_back(FileName); 137 138 std::vector<const char *> ArgsCStr; 139 for (const std::string &arg : Args) { 140 ArgsCStr.push_back(arg.c_str()); 141 } 142 143 CreateInvocationOptions CIOpts; 144 CIOpts.Diags = Diags; 145 CIOpts.VFS = FS; 146 Invocation = createInvocation(ArgsCStr, std::move(CIOpts)); 147 assert(Invocation); 148 Invocation->getFrontendOpts().DisableFree = false; 149 Invocation->getPreprocessorOpts().addRemappedFile( 150 FileName, llvm::MemoryBuffer::getMemBufferCopy(Code).release()); 151 CompilerInstance Compiler; 152 Compiler.setInvocation(Invocation); 153 Compiler.setDiagnostics(Diags.get()); 154 Compiler.setFileManager(FileMgr.get()); 155 Compiler.setSourceManager(SourceMgr.get()); 156 157 syntax::TranslationUnit *Root = nullptr; 158 BuildSyntaxTreeAction Recorder(Root, this->TM, this->TB, this->Arena); 159 160 // Action could not be executed but the frontend didn't identify any errors 161 // in the code ==> problem in setting up the action. 162 if (!Compiler.ExecuteAction(Recorder) && 163 Diags->getClient()->getNumErrors() == 0) { 164 ADD_FAILURE() << "failed to run the frontend"; 165 std::abort(); 166 } 167 return Root; 168 } 169 170 syntax::Node *SyntaxTreeTest::nodeByRange(llvm::Annotations::Range R, 171 syntax::Node *Root) { 172 ArrayRef<syntax::Token> Toks = tokens(Root, *TM); 173 174 if (Toks.front().location().isFileID() && Toks.back().location().isFileID() && 175 syntax::Token::range(*SourceMgr, Toks.front(), Toks.back()) == 176 syntax::FileRange(SourceMgr->getMainFileID(), R.Begin, R.End)) 177 return Root; 178 179 auto *T = dyn_cast<syntax::Tree>(Root); 180 if (!T) 181 return nullptr; 182 for (auto *C = T->getFirstChild(); C != nullptr; C = C->getNextSibling()) { 183 if (auto *Result = nodeByRange(R, C)) 184 return Result; 185 } 186 return nullptr; 187 } 188