Index: include/clang/ASTMatchers/ASTMatchers.h =================================================================== --- include/clang/ASTMatchers/ASTMatchers.h +++ include/clang/ASTMatchers/ASTMatchers.h @@ -806,6 +806,20 @@ /// matches 'friend void foo()'. const internal::VariadicDynCastAllOfMatcher friendDecl; +/// \brief Matches Objective-C method declarations. +/// +/// Given +/// \code +/// @interface AClass +/// - (void)AMethod; +/// @end +/// \endcode +/// objCMethodDecl() +/// matches '- (void)AMethod'. +const internal::VariadicDynCastAllOfMatcher< + Decl, + ObjCMethodDecl> objCMethodDecl; + /// \brief Matches statements. /// /// Given Index: include/clang/Tooling/Tooling.h =================================================================== --- include/clang/Tooling/Tooling.h +++ include/clang/Tooling/Tooling.h @@ -169,7 +169,8 @@ /// /// \return The resulting AST or null if an error occurred. std::unique_ptr buildASTFromCode(const Twine &Code, - const Twine &FileName = "input.cc"); + const Twine &FileName = "input.cc", + bool Reserialize = false); /// \brief Builds an AST for 'Code' with additional flags. /// @@ -181,7 +182,8 @@ std::unique_ptr buildASTFromCodeWithArgs(const Twine &Code, const std::vector &Args, - const Twine &FileName = "input.cc"); + const Twine &FileName = "input.cc", + bool Reserialize = false); /// \brief Utility to run a FrontendAction in a single clang invocation. class ToolInvocation { Index: lib/Tooling/Tooling.cpp =================================================================== --- lib/Tooling/Tooling.cpp +++ lib/Tooling/Tooling.cpp @@ -28,6 +28,7 @@ #include "llvm/Option/Option.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/FileUtilities.h" #include "llvm/Support/Host.h" #include "llvm/Support/raw_ostream.h" @@ -407,14 +408,17 @@ } std::unique_ptr buildASTFromCode(const Twine &Code, - const Twine &FileName) { - return buildASTFromCodeWithArgs(Code, std::vector(), FileName); + const Twine &FileName, + bool Reserialize) { + return buildASTFromCodeWithArgs(Code, std::vector(), FileName, + Reserialize); } std::unique_ptr buildASTFromCodeWithArgs(const Twine &Code, const std::vector &Args, - const Twine &FileName) { + const Twine &FileName, + bool Reserialize) { SmallString<16> FileNameStorage; StringRef FileNameRef = FileName.toNullTerminatedStringRef(FileNameStorage); @@ -430,6 +434,32 @@ return nullptr; assert(ASTs.size() == 1); + + if (Reserialize) { + SmallString<64> ASTFileName; + std::error_code EC = llvm::sys::fs::createTemporaryFile("input", "ast", + ASTFileName); + if (EC) { + llvm::errs() << "createTemporaryFile failed: " << EC.message() << "\n"; + return nullptr; + } + llvm::FileRemover ASTFileRemover(ASTFileName); + + if (ASTs[0]->Save(ASTFileName)) { + llvm::errs() << "Save as AST file " << ASTFileName.str() << " failed\n"; + return nullptr; + } + + IntrusiveRefCntPtr Diags = + CompilerInstance::createDiagnostics(new DiagnosticOptions()); + ASTs[0] = ASTUnit::LoadFromASTFile(ASTFileName.str(), Diags, + FileSystemOptions()); + if (!ASTs[0]) { + llvm::errs() << "Load from AST file " << ASTFileName.str() << " failed\n"; + return nullptr; + } + } + return std::move(ASTs[0]); } Index: unittests/CMakeLists.txt =================================================================== --- unittests/CMakeLists.txt +++ unittests/CMakeLists.txt @@ -27,3 +27,4 @@ if(NOT WIN32) add_subdirectory(libclang) endif() +add_subdirectory(Serialization) Index: unittests/Makefile =================================================================== --- unittests/Makefile +++ unittests/Makefile @@ -15,7 +15,7 @@ IS_UNITTEST_LEVEL := 1 CLANG_LEVEL := .. PARALLEL_DIRS = CodeGen Basic Lex Driver Format ASTMatchers AST Tooling \ - Rewrite Sema + Rewrite Sema Serialization include $(CLANG_LEVEL)/../..//Makefile.config Index: unittests/Serialization/CMakeLists.txt =================================================================== --- unittests/Serialization/CMakeLists.txt +++ unittests/Serialization/CMakeLists.txt @@ -0,0 +1,15 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_unittest(SerializationTests + Reserialization.cpp + ) + +target_link_libraries(SerializationTests + clangAST + clangASTMatchers + clangBasic + clangFrontend + clangTooling + ) Index: unittests/Serialization/Makefile =================================================================== --- unittests/Serialization/Makefile +++ unittests/Serialization/Makefile @@ -0,0 +1,19 @@ +##===- unittests/Serialization/Makefile --------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL = ../.. +TESTNAME = Serialization +include $(CLANG_LEVEL)/../../Makefile.config +LINK_COMPONENTS := $(TARGETS_TO_BUILD) asmparser bitreader support mc option +USEDLIBS = clangTooling.a clangFrontend.a clangSerialization.a clangDriver.a \ + clangRewrite.a clangRewriteFrontend.a \ + clangParse.a clangSema.a clangAnalysis.a \ + clangEdit.a clangAST.a clangASTMatchers.a clangLex.a clangBasic.a + +include $(CLANG_LEVEL)/unittests/Makefile Index: unittests/Serialization/Reserialization.cpp =================================================================== --- unittests/Serialization/Reserialization.cpp +++ unittests/Serialization/Reserialization.cpp @@ -0,0 +1,75 @@ +//===- unittests/Serialization/Reserialization.cpp - Reserialization tests ===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Unit tests for serializing and deserializing the AST. +// +//===----------------------------------------------------------------------===// + +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::tooling; + +// This is a regression test for PR23175. After deserializing an AST containing +// ObjCCategoryImplDecl nodes, attempts to iterate ObjCMethodDecl nodes declared +// in the category interface or implementation resulted in an infinite loop. +TEST(Reserialization, ObjCMethodDeclIteration) { + std::unique_ptr AST(tooling::buildASTFromCode( + "__attribute__((objc_root_class))\n" + "@interface AClass\n" + "-(void)AMethod;\n" // #0 (Redeclared by #4) + "@end\n" + "@interface AClass (ACategory)\n" + "-(void)ACategoryMethod;\n" // #1 (Redeclared by #2) + "@end\n" + "@implementation AClass (ACategory)\n" + "-(void)ACategoryMethod {}\n" // #2 (Redeclares #1) + "@end\n" + "@interface AClass ()\n" + "-(void)AClassExtensionMethod;\n" // #3 (Not redeclared) + "@end\n" + "@implementation AClass\n" + "-(void)AMethod {}\n" // #4 (Redeclares #0) + "-(void)AClassExtensionMethod {}\n" // #5 (Not a redeclaration) + "@end\n" + "", + "input.m", + /*Reserialize*/ true)); + ASSERT_TRUE(AST.get()); + ASTContext &Context = AST->getASTContext(); + + MatchFinder Finder; + internal::CollectMatchesCallback Callback; + Finder.addMatcher( + objCMethodDecl().bind("id"), + &Callback); + Finder.matchAST(Context); + ASSERT_TRUE(Callback.Nodes.size() == 6); + + const int ExpectedDeclCounts[6] = { 2, 2, 2, 1, 2, 1 }; + for (std::size_t i = 0; i < Callback.Nodes.size(); ++i) { + const ObjCMethodDecl *MD = + Callback.Nodes[i].getNodeAs("id"); + ASSERT_TRUE(MD); + + int DeclCounts = 0; + for (const auto *D : MD->redecls()) { + (void)D; + ++DeclCounts; + if (DeclCounts > ExpectedDeclCounts[i]) { + // Avoid infinite loops. Going one too far is far enough. + break; + } + } + ASSERT_TRUE(DeclCounts == ExpectedDeclCounts[i]); + } +}