diff --git a/clang/unittests/Tooling/Syntax/BuildTreeTest.cpp b/clang/unittests/Tooling/Syntax/BuildTreeTest.cpp --- a/clang/unittests/Tooling/Syntax/BuildTreeTest.cpp +++ b/clang/unittests/Tooling/Syntax/BuildTreeTest.cpp @@ -12,8 +12,9 @@ #include "TreeTestBase.h" -namespace clang { -namespace syntax { +using namespace clang; +using namespace clang::syntax; + namespace { TEST_P(SyntaxTreeTest, Simple) { @@ -4836,26 +4837,4 @@ )txt")); } -static std::vector 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; -} - -INSTANTIATE_TEST_CASE_P(SyntaxTreeTests, SyntaxTreeTest, - testing::ValuesIn(allTestClangConfigs()), ); - } // namespace -} // namespace syntax -} // namespace clang diff --git a/clang/unittests/Tooling/Syntax/CMakeLists.txt b/clang/unittests/Tooling/Syntax/CMakeLists.txt --- a/clang/unittests/Tooling/Syntax/CMakeLists.txt +++ b/clang/unittests/Tooling/Syntax/CMakeLists.txt @@ -3,6 +3,7 @@ ) add_clang_unittest(SyntaxTests + TreeTestBase.cpp BuildTreeTest.cpp MutationsTest.cpp TokensTest.cpp diff --git a/clang/unittests/Tooling/Syntax/MutationsTest.cpp b/clang/unittests/Tooling/Syntax/MutationsTest.cpp --- a/clang/unittests/Tooling/Syntax/MutationsTest.cpp +++ b/clang/unittests/Tooling/Syntax/MutationsTest.cpp @@ -12,9 +12,11 @@ #include "clang/Tooling/Syntax/Mutations.h" #include "TreeTestBase.h" +#include "clang/Tooling/Syntax/BuildTree.h" + +using namespace clang; +using namespace clang::syntax; -namespace clang { -namespace syntax { namespace { TEST_P(SyntaxTreeTest, Mutations) { @@ -81,5 +83,3 @@ EXPECT_TRUE(S->isDetached()); } } // namespace -} // namespace syntax -} // namespace clang diff --git a/clang/unittests/Tooling/Syntax/TreeTestBase.h b/clang/unittests/Tooling/Syntax/TreeTestBase.h --- a/clang/unittests/Tooling/Syntax/TreeTestBase.h +++ b/clang/unittests/Tooling/Syntax/TreeTestBase.h @@ -1,4 +1,4 @@ -//===- TreeTestBase.cpp ---------------------------------------------------===// +//===- TreeTestBase.h -----------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -10,184 +10,33 @@ // //===----------------------------------------------------------------------===// -#include "clang/AST/ASTConsumer.h" +#ifndef LLVM_CLANG_UNITTESTS_TOOLING_SYNTAX_TREETESTBASE_H +#define LLVM_CLANG_UNITTESTS_TOOLING_SYNTAX_TREETESTBASE_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" namespace clang { namespace syntax { -static 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->token(), 1); - auto *T = cast(N); - return llvm::makeArrayRef(T->firstLeaf()->token(), - T->lastLeaf()->token() + 1); -} - class SyntaxTreeTest : public ::testing::Test, public ::testing::WithParamInterface { protected: // Build a syntax tree for the code. - syntax::TranslationUnit *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.getTranslationUnitDecl()); - } - - 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()); + TranslationUnit *buildTree(StringRef Code, + const TestClangConfig &ClangConfig); - // 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; - } - - ::testing::AssertionResult treeDumpEqual(StringRef Code, StringRef Tree) { - SCOPED_TRACE(llvm::join(GetParam().getCommandLineArgs(), " ")); - - auto *Root = buildTree(Code, GetParam()); - if (Diags->getClient()->getNumErrors() != 0) { - return ::testing::AssertionFailure() - << "Source file has syntax errors, they were printed to the test " - "log"; - } - std::string Actual = std::string(StringRef(Root->dump(*Arena)).trim()); - // EXPECT_EQ shows the diff between the two strings if they are different. - EXPECT_EQ(Tree.trim().str(), Actual); - if (Actual != Tree.trim().str()) { - return ::testing::AssertionFailure(); - } - return ::testing::AssertionSuccess(); - } - - // Adds a file to the test VFS. - void addFile(StringRef Path, StringRef Contents) { - if (!FS->addFile(Path, time_t(), - llvm::MemoryBuffer::getMemBufferCopy(Contents))) { - ADD_FAILURE() << "could not add a file to VFS: " << Path; - } - } + ::testing::AssertionResult treeDumpEqual(StringRef Code, StringRef Tree); /// Finds the deepest node in the tree that covers exactly \p R. /// FIXME: implement this efficiently and move to public syntax tree API. - syntax::Node *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->firstChild(); C != nullptr; C = C->nextSibling()) { - if (auto *Result = nodeByRange(R, C)) - return Result; - } - return nullptr; - } + syntax::Node *nodeByRange(llvm::Annotations::Range R, syntax::Node *Root); // Data fields. IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); @@ -206,3 +55,4 @@ }; } // namespace syntax } // namespace clang +#endif // LLVM_CLANG_UNITTESTS_TOOLING_SYNTAX_TREETESTBASE_H diff --git a/clang/unittests/Tooling/Syntax/TreeTestBase.cpp b/clang/unittests/Tooling/Syntax/TreeTestBase.cpp new file mode 100644 --- /dev/null +++ b/clang/unittests/Tooling/Syntax/TreeTestBase.cpp @@ -0,0 +1,200 @@ +//===- 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->token(), 1); + auto *T = cast(N); + return llvm::makeArrayRef(T->firstLeaf()->token(), + T->lastLeaf()->token() + 1); +} + +std::vector 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; +} + +INSTANTIATE_TEST_CASE_P(SyntaxTreeTests, SyntaxTreeTest, + testing::ValuesIn(allTestClangConfigs()), ); +} // namespace + +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.getTranslationUnitDecl()); + } + + 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; +} + +::testing::AssertionResult SyntaxTreeTest::treeDumpEqual(StringRef Code, + StringRef Tree) { + SCOPED_TRACE(llvm::join(GetParam().getCommandLineArgs(), " ")); + + auto *Root = buildTree(Code, GetParam()); + if (Diags->getClient()->getNumErrors() != 0) { + return ::testing::AssertionFailure() + << "Source file has syntax errors, they were printed to the test " + "log"; + } + std::string Actual = std::string(StringRef(Root->dump(*Arena)).trim()); + // EXPECT_EQ shows the diff between the two strings if they are different. + EXPECT_EQ(Tree.trim().str(), Actual); + if (Actual != Tree.trim().str()) { + return ::testing::AssertionFailure(); + } + return ::testing::AssertionSuccess(); +} + +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->firstChild(); C != nullptr; C = C->nextSibling()) { + if (auto *Result = nodeByRange(R, C)) + return Result; + } + return nullptr; +}