179 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			179 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===- 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;
 | |
| }
 |