| //===- TreeTestBase.cpp ---------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file provides the test infrastructure for syntax trees. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "TreeTestBase.h" |
| #include "clang/AST/ASTConsumer.h" |
| #include "clang/Basic/LLVM.h" |
| #include "clang/Frontend/CompilerInstance.h" |
| #include "clang/Frontend/CompilerInvocation.h" |
| #include "clang/Frontend/FrontendAction.h" |
| #include "clang/Frontend/TextDiagnosticPrinter.h" |
| #include "clang/Lex/PreprocessorOptions.h" |
| #include "clang/Testing/CommandLineArgs.h" |
| #include "clang/Testing/TestClangConfig.h" |
| #include "clang/Tooling/Syntax/BuildTree.h" |
| #include "clang/Tooling/Syntax/Nodes.h" |
| #include "clang/Tooling/Syntax/Tokens.h" |
| #include "clang/Tooling/Syntax/Tree.h" |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Testing/Support/Annotations.h" |
| #include "gtest/gtest.h" |
| |
| using namespace clang; |
| using namespace clang::syntax; |
| |
| namespace { |
| ArrayRef<syntax::Token> tokens(syntax::Node *N) { |
| assert(N->isOriginal() && "tokens of modified nodes are not well-defined"); |
| if (auto *L = dyn_cast<syntax::Leaf>(N)) |
| return llvm::makeArrayRef(L->getToken(), 1); |
| auto *T = cast<syntax::Tree>(N); |
| return llvm::makeArrayRef(T->findFirstLeaf()->getToken(), |
| T->findLastLeaf()->getToken() + 1); |
| } |
| } // namespace |
| |
| std::vector<TestClangConfig> clang::syntax::allTestClangConfigs() { |
| std::vector<TestClangConfig> all_configs; |
| for (TestLanguage lang : {Lang_C89, Lang_C99, Lang_CXX03, Lang_CXX11, |
| Lang_CXX14, Lang_CXX17, Lang_CXX20}) { |
| TestClangConfig config; |
| config.Language = lang; |
| config.Target = "x86_64-pc-linux-gnu"; |
| all_configs.push_back(config); |
| |
| // Windows target is interesting to test because it enables |
| // `-fdelayed-template-parsing`. |
| config.Target = "x86_64-pc-win32-msvc"; |
| all_configs.push_back(config); |
| } |
| return all_configs; |
| } |
| |
| syntax::TranslationUnit * |
| SyntaxTreeTest::buildTree(StringRef Code, const TestClangConfig &ClangConfig) { |
| // FIXME: this code is almost the identical to the one in TokensTest. Share |
| // it. |
| class BuildSyntaxTree : public ASTConsumer { |
| public: |
| BuildSyntaxTree(syntax::TranslationUnit *&Root, |
| std::unique_ptr<syntax::TokenBuffer> &TB, |
| std::unique_ptr<syntax::Arena> &Arena, |
| std::unique_ptr<syntax::TokenCollector> Tokens) |
| : Root(Root), TB(TB), Arena(Arena), Tokens(std::move(Tokens)) { |
| assert(this->Tokens); |
| } |
| |
| void HandleTranslationUnit(ASTContext &Ctx) override { |
| TB = std::make_unique<syntax::TokenBuffer>(std::move(*Tokens).consume()); |
| Tokens = nullptr; // make sure we fail if this gets called twice. |
| Arena = std::make_unique<syntax::Arena>(Ctx.getSourceManager(), |
| Ctx.getLangOpts(), *TB); |
| Root = syntax::buildSyntaxTree(*Arena, Ctx); |
| } |
| |
| private: |
| syntax::TranslationUnit *&Root; |
| std::unique_ptr<syntax::TokenBuffer> &TB; |
| std::unique_ptr<syntax::Arena> &Arena; |
| std::unique_ptr<syntax::TokenCollector> Tokens; |
| }; |
| |
| class BuildSyntaxTreeAction : public ASTFrontendAction { |
| public: |
| BuildSyntaxTreeAction(syntax::TranslationUnit *&Root, |
| std::unique_ptr<syntax::TokenBuffer> &TB, |
| std::unique_ptr<syntax::Arena> &Arena) |
| : Root(Root), TB(TB), Arena(Arena) {} |
| |
| std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, |
| StringRef InFile) override { |
| // We start recording the tokens, ast consumer will take on the result. |
| auto Tokens = |
| std::make_unique<syntax::TokenCollector>(CI.getPreprocessor()); |
| return std::make_unique<BuildSyntaxTree>(Root, TB, Arena, |
| std::move(Tokens)); |
| } |
| |
| private: |
| syntax::TranslationUnit *&Root; |
| std::unique_ptr<syntax::TokenBuffer> &TB; |
| std::unique_ptr<syntax::Arena> &Arena; |
| }; |
| |
| constexpr const char *FileName = "./input.cpp"; |
| FS->addFile(FileName, time_t(), llvm::MemoryBuffer::getMemBufferCopy("")); |
| |
| if (!Diags->getClient()) |
| Diags->setClient(new TextDiagnosticPrinter(llvm::errs(), DiagOpts.get())); |
| Diags->setSeverityForGroup(diag::Flavor::WarningOrError, "unused-value", |
| diag::Severity::Ignored, SourceLocation()); |
| |
| // Prepare to run a compiler. |
| std::vector<std::string> Args = { |
| "syntax-test", |
| "-fsyntax-only", |
| }; |
| llvm::copy(ClangConfig.getCommandLineArgs(), std::back_inserter(Args)); |
| Args.push_back(FileName); |
| |
| std::vector<const char *> ArgsCStr; |
| for (const std::string &arg : Args) { |
| ArgsCStr.push_back(arg.c_str()); |
| } |
| |
| Invocation = createInvocationFromCommandLine(ArgsCStr, Diags, FS); |
| assert(Invocation); |
| Invocation->getFrontendOpts().DisableFree = false; |
| Invocation->getPreprocessorOpts().addRemappedFile( |
| FileName, llvm::MemoryBuffer::getMemBufferCopy(Code).release()); |
| CompilerInstance Compiler; |
| Compiler.setInvocation(Invocation); |
| Compiler.setDiagnostics(Diags.get()); |
| Compiler.setFileManager(FileMgr.get()); |
| Compiler.setSourceManager(SourceMgr.get()); |
| |
| syntax::TranslationUnit *Root = nullptr; |
| BuildSyntaxTreeAction Recorder(Root, this->TB, this->Arena); |
| |
| // Action could not be executed but the frontend didn't identify any errors |
| // in the code ==> problem in setting up the action. |
| if (!Compiler.ExecuteAction(Recorder) && |
| Diags->getClient()->getNumErrors() == 0) { |
| ADD_FAILURE() << "failed to run the frontend"; |
| std::abort(); |
| } |
| return Root; |
| } |
| |
| syntax::Node *SyntaxTreeTest::nodeByRange(llvm::Annotations::Range R, |
| syntax::Node *Root) { |
| ArrayRef<syntax::Token> Toks = tokens(Root); |
| |
| if (Toks.front().location().isFileID() && Toks.back().location().isFileID() && |
| syntax::Token::range(*SourceMgr, Toks.front(), Toks.back()) == |
| syntax::FileRange(SourceMgr->getMainFileID(), R.Begin, R.End)) |
| return Root; |
| |
| auto *T = dyn_cast<syntax::Tree>(Root); |
| if (!T) |
| return nullptr; |
| for (auto *C = T->getFirstChild(); C != nullptr; C = C->getNextSibling()) { |
| if (auto *Result = nodeByRange(R, C)) |
| return Result; |
| } |
| return nullptr; |
| } |