//===- 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 tokens(syntax::Node *N) { assert(N->isOriginal() && "tokens of modified nodes are not well-defined"); if (auto *L = dyn_cast(N)) return llvm::makeArrayRef(L->getToken(), 1); auto *T = cast(N); return llvm::makeArrayRef(T->findFirstLeaf()->getToken(), T->findLastLeaf()->getToken() + 1); } } // namespace std::vector clang::syntax::allTestClangConfigs() { std::vector 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 &TB, std::unique_ptr &Arena, std::unique_ptr Tokens) : Root(Root), TB(TB), Arena(Arena), Tokens(std::move(Tokens)) { assert(this->Tokens); } void HandleTranslationUnit(ASTContext &Ctx) override { TB = std::make_unique(std::move(*Tokens).consume()); Tokens = nullptr; // make sure we fail if this gets called twice. Arena = std::make_unique(Ctx.getSourceManager(), Ctx.getLangOpts(), *TB); Root = syntax::buildSyntaxTree(*Arena, Ctx); } private: syntax::TranslationUnit *&Root; std::unique_ptr &TB; std::unique_ptr &Arena; std::unique_ptr Tokens; }; class BuildSyntaxTreeAction : public ASTFrontendAction { public: BuildSyntaxTreeAction(syntax::TranslationUnit *&Root, std::unique_ptr &TB, std::unique_ptr &Arena) : Root(Root), TB(TB), Arena(Arena) {} std::unique_ptr CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { // We start recording the tokens, ast consumer will take on the result. auto Tokens = std::make_unique(CI.getPreprocessor()); return std::make_unique(Root, TB, Arena, std::move(Tokens)); } private: syntax::TranslationUnit *&Root; std::unique_ptr &TB; std::unique_ptr &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 Args = { "syntax-test", "-fsyntax-only", }; llvm::copy(ClangConfig.getCommandLineArgs(), std::back_inserter(Args)); Args.push_back(FileName); std::vector 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 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(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; }