diff --git a/clang/lib/Tooling/CMakeLists.txt b/clang/lib/Tooling/CMakeLists.txt --- a/clang/lib/Tooling/CMakeLists.txt +++ b/clang/lib/Tooling/CMakeLists.txt @@ -8,10 +8,75 @@ add_subdirectory(Inclusions) add_subdirectory(Refactoring) add_subdirectory(ASTDiff) +add_subdirectory(DumpTool) add_subdirectory(Syntax) add_subdirectory(DependencyScanning) add_subdirectory(Transformer) +find_package(Python3 COMPONENTS Interpreter) + +add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/ASTNodeAPI.json + DEPENDS clang-srcloc-dump clang-headers + COMMAND + $ + # Skip this in debug mode because parsing AST.h is too slow + --skip-processing=$ + --astheader=${CMAKE_SOURCE_DIR}/../clang/include/clang/AST/AST.h + -I ${CMAKE_BINARY_DIR}/lib/clang/${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}/include + -I ${CMAKE_SOURCE_DIR}/../clang/include + -I ${CMAKE_BINARY_DIR}/tools/clang/include/ + -I ${CMAKE_BINARY_DIR}/include + -I ${CMAKE_SOURCE_DIR}/include + --json-output-path ${CMAKE_BINARY_DIR}/ASTNodeAPI.json +) + +add_custom_target(run-ast-api-dump-tool + DEPENDS ${CMAKE_BINARY_DIR}/ASTNodeAPI.json +) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/NodeLocationIntrospection.h + ${CMAKE_CURRENT_BINARY_DIR}/NodeLocationIntrospection.cpp + DEPENDS ${CMAKE_BINARY_DIR}/ASTNodeAPI.json ${CMAKE_CURRENT_SOURCE_DIR}/DumpTool/generate_cxx_src_locs.py + COMMAND + ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/DumpTool/generate_cxx_src_locs.py + --json-input-path ${CMAKE_BINARY_DIR}/ASTNodeAPI.json + --relative-base clang/Tooling/NodeLocationIntrospection +) + +add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/tools/clang/include/clang/Tooling/NodeLocationIntrospection.h + ${CMAKE_CURRENT_BINARY_DIR}/generated/NodeLocationIntrospection.cpp + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/NodeLocationIntrospection.h + ${CMAKE_CURRENT_BINARY_DIR}/NodeLocationIntrospection.cpp + clang-format + COMMAND + ${CMAKE_COMMAND} -E make_directory + ${CMAKE_CURRENT_BINARY_DIR}/generated/ + ${CMAKE_CURRENT_BINARY_DIR}/formatted/ + COMMAND + clang-format ${CMAKE_CURRENT_BINARY_DIR}/NodeLocationIntrospection.h > + ${CMAKE_CURRENT_BINARY_DIR}/formatted/NodeLocationIntrospection.h + COMMAND + clang-format ${CMAKE_CURRENT_BINARY_DIR}/NodeLocationIntrospection.cpp > + ${CMAKE_CURRENT_BINARY_DIR}/formatted/NodeLocationIntrospection.cpp + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_BINARY_DIR}/formatted/NodeLocationIntrospection.h + ${CMAKE_BINARY_DIR}/tools/clang/include/clang/Tooling/NodeLocationIntrospection.h + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_BINARY_DIR}/formatted/NodeLocationIntrospection.cpp + ${CMAKE_CURRENT_BINARY_DIR}/generated/NodeLocationIntrospection.cpp +) + +add_custom_target(run-ast-api-generate-tool + DEPENDS + ${CMAKE_BINARY_DIR}/tools/clang/include/clang/Tooling/NodeLocationIntrospection.h + ${CMAKE_CURRENT_BINARY_DIR}/generated/NodeLocationIntrospection.cpp +) + add_clang_library(clangTooling AllTUsExecution.cpp ArgumentsAdjusters.cpp @@ -29,6 +94,8 @@ StandaloneExecution.cpp Tooling.cpp + ${CMAKE_CURRENT_BINARY_DIR}/generated/NodeLocationIntrospection.cpp + DEPENDS ClangDriverOptions omp_gen diff --git a/clang/lib/Tooling/DumpTool/APIData.h b/clang/lib/Tooling/DumpTool/APIData.h new file mode 100644 --- /dev/null +++ b/clang/lib/Tooling/DumpTool/APIData.h @@ -0,0 +1,31 @@ +//===- srclocdumper.cpp --------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_APIDATA_H +#define LLVM_CLANG_TOOLING_APIDATA_H + +#include +#include + +namespace clang { +namespace tooling { + +struct ClassData { + + bool IsEmpty() const { return Locs.empty() && Rngs.empty(); } + + std::vector Locs; + std::vector Rngs; + // TODO: Extend this with locations available via typelocs etc. +}; + +} // namespace tooling +} // namespace clang + +#endif diff --git a/clang/lib/Tooling/DumpTool/ASTSrcLocProcessor.h b/clang/lib/Tooling/DumpTool/ASTSrcLocProcessor.h new file mode 100644 --- /dev/null +++ b/clang/lib/Tooling/DumpTool/ASTSrcLocProcessor.h @@ -0,0 +1,49 @@ +//===- ASTSrcLocProcessor.h -----------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_ASTSRCLOCPROCESSOR_H +#define LLVM_CLANG_TOOLING_ASTSRCLOCPROCESSOR_H + +#include "clang/ASTMatchers/ASTMatchFinder.h" + +#include "APIData.h" + +#include +#include +#include + +namespace clang { + +class CompilerInstance; + +namespace tooling { + +class ASTSrcLocProcessor : public ast_matchers::MatchFinder::MatchCallback { +public: + ASTSrcLocProcessor(StringRef JsonPath); + + std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, + StringRef File); + + void Generate(); + +private: + void run(const ast_matchers::MatchFinder::MatchResult &Result) override; + + llvm::StringMap ClassInheritance; + llvm::StringMap> ClassesInClade; + llvm::StringMap ClassEntries; + + std::string JsonPath; +}; + +} // namespace tooling +} // namespace clang + +#endif diff --git a/clang/lib/Tooling/DumpTool/ASTSrcLocProcessor.cpp b/clang/lib/Tooling/DumpTool/ASTSrcLocProcessor.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Tooling/DumpTool/ASTSrcLocProcessor.cpp @@ -0,0 +1,169 @@ +//===- ASTSrcLocProcessor.cpp ---------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ASTSrcLocProcessor.h" + +#include "clang/Frontend/CompilerInstance.h" +#include "llvm/Support/JSON.h" + +using namespace clang::tooling; +using namespace llvm; +using namespace clang::ast_matchers; + +ASTSrcLocProcessor::ASTSrcLocProcessor(StringRef JsonPath) + : JsonPath(JsonPath) {} + +auto publicAccessor = [](auto... InnerMatcher) { + return cxxMethodDecl(isPublic(), parameterCountIs(0), isConst(), + InnerMatcher...); +}; + +std::unique_ptr +ASTSrcLocProcessor::CreateASTConsumer(clang::CompilerInstance &Compiler, + StringRef File) { + + MatchFinder::MatchFinderOptions FinderOptions; + + auto Finder = std::make_unique(std::move(FinderOptions)); + + Finder->addMatcher( + cxxRecordDecl( + isDefinition(), + isSameOrDerivedFrom( + // TODO: Extend this with other clades + namedDecl(hasName("clang::Stmt")).bind("nodeClade")), + optionally(isDerivedFrom(cxxRecordDecl().bind("derivedFrom")))) + .bind("className"), + this); + + return Finder.release()->newASTConsumer(); +} + +llvm::json::Value toJSON(llvm::StringMap> const &Obj) { + using llvm::json::toJSON; + + llvm::json::Object JsonObj; + for (const auto &Item : Obj) { + JsonObj[Item.first()] = Item.second; + } + return llvm::json::Value{std::move(JsonObj)}; +} + +llvm::json::Value toJSON(llvm::StringMap const &Obj) { + using llvm::json::toJSON; + + llvm::json::Object JsonObj; + for (const auto &Item : Obj) { + JsonObj[Item.first()] = Item.second; + } + return llvm::json::Value{std::move(JsonObj)}; +} + +llvm::json::Value toJSON(ClassData const &Obj) { + llvm::json::Object JsonObj; + + if (!Obj.Locs.empty()) + JsonObj["locs"] = Obj.Locs; + if (!Obj.Rngs.empty()) + JsonObj["rngs"] = Obj.Rngs; + return llvm::json::Value{std::move(JsonObj)}; +} + +llvm::json::Value toJSON(llvm::StringMap const &Obj) { + using llvm::json::toJSON; + + llvm::json::Object JsonObj; + for (const auto &Item : Obj) { + if (!Item.second.IsEmpty()) + JsonObj[Item.first()] = ::toJSON(Item.second); + } + return llvm::json::Value{std::move(JsonObj)}; +} + +void WriteJSON(std::string JsonPath, + llvm::StringMap const &ClassInheritance, + llvm::StringMap> const &ClassesInClade, + llvm::StringMap const &ClassEntries) { + llvm::json::Object JsonObj; + + using llvm::json::toJSON; + + JsonObj["classInheritance"] = ::toJSON(ClassInheritance); + JsonObj["classesInClade"] = ::toJSON(ClassesInClade); + JsonObj["classEntries"] = ::toJSON(ClassEntries); + + std::error_code EC; + llvm::raw_fd_ostream JsonOut(JsonPath, EC, llvm::sys::fs::F_Text); + if (EC) + return; + + llvm::json::Value JsonVal(std::move(JsonObj)); + JsonOut << formatv("{0:2}", JsonVal); +} + +void ASTSrcLocProcessor::Generate() { + WriteJSON(JsonPath, ClassInheritance, ClassesInClade, ClassEntries); +} + +std::vector +CaptureMethods(std::string TypeString, const clang::CXXRecordDecl *ASTClass, + const MatchFinder::MatchResult &Result) { + auto BoundNodesVec = + match(findAll(publicAccessor(ofClass(equalsNode(ASTClass)), + returns(asString(TypeString))) + .bind("classMethod")), + *ASTClass, *Result.Context); + + std::vector Methods; + for (const auto &BN : BoundNodesVec) { + if (const auto Node = BN.getNodeAs("classMethod")) { + // Only record the getBeginLoc etc on Stmt etc, because it will call + // more-derived implementations pseudo-virtually + if ((ASTClass->getName() != "Stmt" && ASTClass->getName() != "Decl") && + (Node->getName() == "getBeginLoc" || Node->getName() == "getEndLoc" || + Node->getName() == "getSourceRange")) { + continue; + } + // Only record the getExprLoc on Expr, because it will call + // more-derived implementations pseudo-virtually + if ((ASTClass->getName() != "Expr") && + (Node->getName() == "getExprLoc")) { + continue; + } + Methods.push_back(Node->getName().str()); + } + } + return Methods; +} + +void ASTSrcLocProcessor::run(const MatchFinder::MatchResult &Result) { + + if (const auto *ASTClass = + Result.Nodes.getNodeAs("className")) { + + StringRef ClassName = ASTClass->getName(); + + ClassData CD; + + auto NodeClade = Result.Nodes.getNodeAs("nodeClade"); + auto CladeName = NodeClade->getName(); + + if (auto DerivedFrom = + Result.Nodes.getNodeAs("derivedFrom")) + ClassInheritance[ClassName] = DerivedFrom->getName(); + + CD.Locs = CaptureMethods("class clang::SourceLocation", ASTClass, Result); + CD.Rngs = CaptureMethods("class clang::SourceRange", ASTClass, Result); + + if (!CD.IsEmpty()) { + ClassEntries[ClassName] = CD; + ClassesInClade[CladeName].push_back(ClassName); + } + } +} diff --git a/clang/lib/Tooling/DumpTool/CMakeLists.txt b/clang/lib/Tooling/DumpTool/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/clang/lib/Tooling/DumpTool/CMakeLists.txt @@ -0,0 +1,17 @@ + +add_clang_executable(clang-srcloc-dump + ASTSrcLocProcessor.cpp + ClangSrcLocDump.cpp +) + +target_link_libraries(clang-srcloc-dump + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangFormat + clangFrontend + clangLex + clangRewrite + clangToolingCore +) diff --git a/clang/lib/Tooling/DumpTool/ClangSrcLocDump.cpp b/clang/lib/Tooling/DumpTool/ClangSrcLocDump.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Tooling/DumpTool/ClangSrcLocDump.cpp @@ -0,0 +1,141 @@ +//===- ClangSrcLocDump.cpp ------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/Diagnostic.h" +#include "clang/Driver/Compilation.h" +#include "clang/Driver/Driver.h" +#include "clang/Driver/Job.h" +#include "clang/Driver/Options.h" +#include "clang/Driver/Tool.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/JSON.h" + +#include "ASTSrcLocProcessor.h" + +using namespace clang::tooling; +using namespace clang; +using namespace llvm; + +static cl::list IncludeDirectories( + "I", cl::desc("Include directories to use while compiling"), + cl::value_desc("directory"), cl::Required, cl::OneOrMore, cl::Prefix); + +static cl::opt + AstHeaderFile("astheader", cl::desc("AST header to parse API from"), + cl::Required, cl::value_desc("AST header file")); + +static cl::opt + SkipProcessing("skip-processing", + cl::desc("Avoid processing the AST header file"), + cl::Required, cl::value_desc("bool")); + +static cl::opt JsonOutputPath("json-output-path", + cl::desc("json output path"), + cl::Required, + cl::value_desc("path")); + +class ASTSrcLocGenerationAction : public clang::ASTFrontendAction { +public: + ASTSrcLocGenerationAction() : Processor(JsonOutputPath) {} + + ~ASTSrcLocGenerationAction() { Processor.Generate(); } + + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &Compiler, + llvm::StringRef File) override { + return Processor.CreateASTConsumer(Compiler, File); + } + +private: + ASTSrcLocProcessor Processor; +}; + +int main(int argc, const char **argv) { + + cl::ParseCommandLineOptions(argc, argv); + + if (SkipProcessing) { + std::error_code EC; + llvm::raw_fd_ostream jsonOut(JsonOutputPath, EC, llvm::sys::fs::F_Text); + if (EC) + return 1; + jsonOut << formatv("{0:2}", llvm::json::Value(llvm::json::Object())); + return 0; + } + + std::vector Args; + Args.push_back("-xc++-header"); + + for (auto I = IncludeDirectories.begin(), E = IncludeDirectories.end(); + I != E; ++I) { + Args.push_back("-I" + *I); + } + + Args.push_back(AstHeaderFile); + + std::vector Argv; + for (const auto &Str : Args) + Argv.push_back(Str.c_str()); + + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); + unsigned MissingArgIndex, MissingArgCount; + auto Opts = driver::getDriverOptTable(); + auto ParsedArgs = Opts.ParseArgs(llvm::makeArrayRef(Argv).slice(1), + MissingArgIndex, MissingArgCount); + ParseDiagnosticArgs(*DiagOpts, ParsedArgs); + TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs()), &*DiagOpts, + &DiagnosticPrinter, false); + + FileManager Files(FileSystemOptions(), vfs::getRealFileSystem()); + + auto Driver = std::make_unique( + "clang++", llvm::sys::getDefaultTargetTriple(), Diagnostics, + "ast-api-dump-tool", &Files.getVirtualFileSystem()); + + const auto Compilation(Driver->BuildCompilation(llvm::makeArrayRef(Argv))); + if (!Compilation) + return 1; + + const auto &Jobs = Compilation->getJobs(); + if (Jobs.size() != 1 || !isa(*Jobs.begin())) { + SmallString<256> error_msg; + llvm::raw_svector_ostream error_stream(error_msg); + Jobs.Print(error_stream, "; ", true); + return 1; + } + + const auto &Cmd = cast(*Jobs.begin()); + const llvm::opt::ArgStringList &CC1Args = Cmd.getArguments(); + + auto Invocation = std::make_unique(); + CompilerInvocation::CreateFromArgs(*Invocation, CC1Args, Diagnostics); + + CompilerInstance Compiler(std::make_shared()); + Compiler.setInvocation(std::move(Invocation)); + + Compiler.createDiagnostics(&DiagnosticPrinter, false); + if (!Compiler.hasDiagnostics()) + return 1; + + Compiler.createSourceManager(Files); + + ASTSrcLocGenerationAction ScopedToolAction; + Compiler.ExecuteAction(ScopedToolAction); + + Files.clearStatCache(); + + return 0; +} diff --git a/clang/lib/Tooling/DumpTool/generate_cxx_src_locs.py b/clang/lib/Tooling/DumpTool/generate_cxx_src_locs.py new file mode 100755 --- /dev/null +++ b/clang/lib/Tooling/DumpTool/generate_cxx_src_locs.py @@ -0,0 +1,275 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import os +import json + +from optparse import OptionParser + +parser = OptionParser() +parser.add_option('--json-input-path', + help='Read API description from FILE', metavar='FILE') +parser.add_option('--relative-base', help='Generate files relative to FILEPATH', + metavar='FILEPATH') + +(options, args) = parser.parse_args() + +with open(options.json_input_path) as f: + jsonData = json.load(f) + + +class Generator(object): + + headerContent = '' + implementationContent = '' + + def GeneratePrologue(self, RelativeBase): + + self.headerContent += \ + """ +/*===- Generated file -------------------------------------------*- C++ -*-===*\ +|* *| +|* Introspection of available AST node SourceLocations *| +|* *| +|* Automatically generated file, do not edit! *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef LLVM_CLANG_TOOLING_LOCATION_NODELOCATIONINTROSPECTION_H +#define LLVM_CLANG_TOOLING_LOCATION_NODELOCATIONINTROSPECTION_H + +#include +#include + +#include + +""" + + self.implementationContent += \ + """ +/*===- Generated file -------------------------------------------*- C++ -*-===*\ +|* *| +|* Introspection of available AST node SourceLocations *| +|* *| +|* Automatically generated file, do not edit! *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#include +""" + + self.headerContent += \ + """ +namespace clang { + +class Stmt; + +namespace tooling { + +namespace internal { +struct RangeLessThan { + bool operator()(std::pair const &LHS, + std::pair const &RHS) const { + if (!LHS.first.isValid() || !RHS.first.isValid()) + return false; + + if (LHS.first.getBegin() < RHS.first.getBegin()) + return true; + else if (LHS.first.getBegin() != RHS.first.getBegin()) + return false; + + if (LHS.first.getEnd() < RHS.first.getEnd()) + return true; + else if (LHS.first.getEnd() != RHS.first.getEnd()) + return false; + + return LHS.second < RHS.second; + } +}; +} // namespace internal + +template >> +using UniqueMultiMap = std::set, Comp>; + +using SourceLocationMap = UniqueMultiMap; +using SourceRangeMap = + UniqueMultiMap; + +struct NodeLocationAccessors { + SourceLocationMap LocationAccessors; + SourceRangeMap RangeAccessors; +}; + +namespace NodeLocationIntrospection { +""" + + self.implementationContent += \ + """ +#include <{0}.h> + +namespace clang {{ +namespace tooling {{ + +using LocationAndString = SourceLocationMap::value_type; +using RangeAndString = SourceRangeMap::value_type; +""".format(RelativeBase) + + def GenerateBaseGetLocationsDeclaration(self, CladeName): + self.implementationContent += \ + """ +void GetLocationsImpl(clang::{0} const *Object, SourceLocationMap &Locs, + SourceRangeMap &Rngs); +""".format(CladeName) + + def GenerateSrcLocMethod(self, ClassName, ClassData): + + self.implementationContent += \ + """ +static void GetLocations{0}(clang::{0} const &Object, + SourceLocationMap &Locs, SourceRangeMap &Rngs) +{{ +""".format(ClassName) + + if 'locs' in ClassData: + for locName in ClassData['locs']: + self.implementationContent += \ + """ + Locs.insert(LocationAndString(Object.{0}(), "{0}")); +""".format(locName) + + self.implementationContent += '\n' + + if 'rngs' in ClassData: + for rngName in ClassData['rngs']: + self.implementationContent += \ + """ + Rngs.insert(RangeAndString(Object.{0}(), "{0}")); +""".format(rngName) + + self.implementationContent += '\n' + + self.implementationContent += '}\n' + + def GenerateFiles(self, RelativeBase): + BaseName = os.path.basename(RelativeBase) + with open(os.path.join(os.getcwd(), + BaseName + '.h'), 'w') as f: + f.write(self.headerContent) + with open(os.path.join(os.getcwd(), + BaseName + '.cpp'), 'w') as f: + f.write(self.implementationContent) + + def GenerateTrivialBaseGetLocationsFunction(self, CladeName): + MethodReturnType = 'NodeLocationAccessors' + + Signature = \ + 'GetLocations(clang::{0} const *Object)'.format(CladeName) + + self.headerContent += MethodReturnType + ' ' + Signature + ';\n' + + self.implementationContent += \ + '{0} NodeLocationIntrospection::{1} {{ return {{}}; }}'.format(MethodReturnType, + Signature) + + def GenerateBaseGetLocationsFunction(self, ASTClassNames, CladeName): + + MethodReturnType = 'NodeLocationAccessors' + + Signature = \ + 'GetLocations(clang::{0} const *Object)'.format(CladeName) + ImplSignature = \ + """ +GetLocationsImpl( + clang::{0} const *Object, SourceLocationMap &Locs, + SourceRangeMap &Rngs) +""".format(CladeName) + + self.headerContent += MethodReturnType + ' ' + Signature + ';\n' + + self.implementationContent += \ + 'void {0} {{ GetLocations{1}(*Object, Locs, Rngs);'.format(ImplSignature, + CladeName) + + for ASTClassName in ASTClassNames: + if ASTClassName != CladeName: + self.implementationContent += \ + """ +if (auto Derived = llvm::dyn_cast(Object)) {{ + GetLocations{0}(*Derived, Locs, Rngs); +}} +""".format(ASTClassName) + + self.implementationContent += '}' + + self.implementationContent += \ + """ +{0} NodeLocationIntrospection::{1} {{ + NodeLocationAccessors Result; + + GetLocationsImpl(Object, Result.LocationAccessors, + Result.RangeAccessors); +""".format(MethodReturnType, + Signature) + + self.implementationContent += 'return Result; }' + + def GenerateDynNodeVisitor(self, CladeNames): + MethodReturnType = 'NodeLocationAccessors' + + Signature = \ + 'GetLocations(clang::ast_type_traits::DynTypedNode const &Node)' + + self.headerContent += MethodReturnType + ' ' + Signature + ';\n' + + self.implementationContent += MethodReturnType \ + + ' NodeLocationIntrospection::' + Signature + '{' + + for CladeName in CladeNames: + self.implementationContent += \ + """ + if (const auto *N = Node.get<{0}>()) + return GetLocations(const_cast<{0} *>(N));""".format(CladeName) + + self.implementationContent += 'return {}; }' + + def GenerateEpilogue(self): + + self.headerContent += '}' + + self.headerContent += ''' + } +} +#endif +''' + + self.implementationContent += ''' + } +} +''' + + +g = Generator() + +g.GeneratePrologue(options.relative_base) + +if 'classesInClade' in jsonData: + for (CladeName, ClassNameData) in jsonData['classesInClade'][0].items(): + g.GenerateBaseGetLocationsDeclaration(CladeName) + + for (ClassName, ClassAccessors) in jsonData['classEntries'][0].items(): + if ClassAccessors: + g.GenerateSrcLocMethod(ClassName, ClassAccessors[0]) + + for (CladeName, ClassNameData) in jsonData['classesInClade'][0].items(): + g.GenerateBaseGetLocationsFunction(ClassNameData, CladeName) + + g.GenerateDynNodeVisitor(jsonData['classesInClade'][0].keys()) +else: + for CladeName in ['Stmt']: + g.GenerateTrivialBaseGetLocationsFunction(CladeName) + + g.GenerateDynNodeVisitor([]) + +g.GenerateEpilogue() + +g.GenerateFiles(options.relative_base) diff --git a/clang/unittests/CMakeLists.txt b/clang/unittests/CMakeLists.txt --- a/clang/unittests/CMakeLists.txt +++ b/clang/unittests/CMakeLists.txt @@ -30,6 +30,7 @@ add_subdirectory(AST) add_subdirectory(CrossTU) add_subdirectory(Tooling) +add_subdirectory(Introspection) add_subdirectory(Format) add_subdirectory(Rewrite) add_subdirectory(Sema) diff --git a/clang/unittests/Introspection/CMakeLists.txt b/clang/unittests/Introspection/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/clang/unittests/Introspection/CMakeLists.txt @@ -0,0 +1,22 @@ +set(LLVM_LINK_COMPONENTS + ${LLVM_TARGETS_TO_BUILD} + FrontendOpenMP + Support + ) + +add_clang_unittest(IntrospectionTests + IntrospectionTest.cpp + ) + +clang_target_link_libraries(IntrospectionTests + PRIVATE + clangAST + clangTooling + ) +target_compile_definitions(IntrospectionTests PRIVATE + SKIP_INTROSPECTION_GENERATION=$ +) +target_link_libraries(IntrospectionTests + PRIVATE + LLVMTestingSupport +) diff --git a/clang/unittests/Introspection/IntrospectionTest.cpp b/clang/unittests/Introspection/IntrospectionTest.cpp new file mode 100644 --- /dev/null +++ b/clang/unittests/Introspection/IntrospectionTest.cpp @@ -0,0 +1,89 @@ +//===- unittest/Introspection/IntrospectionTest.cpp - Introspection test -===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Tests for AST location API introspection. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Tooling/NodeLocationIntrospection.h" +#include "clang/Tooling/Tooling.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace clang; +using namespace clang::ast_matchers; + +#if SKIP_INTROSPECTION_GENERATION + +TEST(Introspection, NonFatalAPI) { + auto AST = tooling::buildASTFromCode( + "void foo() {} void bar() { foo(); }", "foo.cpp", + std::make_shared()); + auto &Ctx = AST->getASTContext(); + auto &TU = *Ctx.getTranslationUnitDecl(); + + auto BoundNodes = ast_matchers::match( + decl(hasDescendant( + callExpr(callee(functionDecl(hasName("foo")))).bind("fooCall"))), + TU, Ctx); + + EXPECT_EQ(BoundNodes.size(), 1u); + + auto FooCall = BoundNodes[0].getNodeAs("fooCall"); + + auto result = tooling::NodeLocationIntrospection::GetLocations(FooCall); + + EXPECT_EQ(result.LocationAccessors.size(), 0); + EXPECT_EQ(result.RangeAccessors.size(), 0); +} + +#else + +TEST(Introspection, SourceLocations) { + auto AST = tooling::buildASTFromCode( + "void foo() {} void bar() { foo(); }", "foo.cpp", + std::make_shared()); + auto &Ctx = AST->getASTContext(); + auto &TU = *Ctx.getTranslationUnitDecl(); + + auto BoundNodes = ast_matchers::match( + decl(hasDescendant( + callExpr(callee(functionDecl(hasName("foo")))).bind("fooCall"))), + TU, Ctx); + + EXPECT_EQ(BoundNodes.size(), 1u); + + auto FooCall = BoundNodes[0].getNodeAs("fooCall"); + + auto result = tooling::NodeLocationIntrospection::GetLocations(FooCall); + + EXPECT_EQ(result.LocationAccessors.size(), 4u); + + EXPECT_EQ(result.LocationAccessors.begin()->first, FooCall->getBeginLoc()); + EXPECT_EQ(result.LocationAccessors.begin()->second, "getBeginLoc"); + EXPECT_EQ(std::next(result.LocationAccessors.begin(), 1)->first, + FooCall->getExprLoc()); + EXPECT_EQ(std::next(result.LocationAccessors.begin(), 1)->second, + "getExprLoc"); + EXPECT_EQ(std::next(result.LocationAccessors.begin(), 2)->first, + FooCall->getEndLoc()); + EXPECT_EQ(std::next(result.LocationAccessors.begin(), 2)->second, + "getEndLoc"); + EXPECT_EQ(std::next(result.LocationAccessors.begin(), 3)->first, + FooCall->getRParenLoc()); + EXPECT_EQ(std::next(result.LocationAccessors.begin(), 3)->second, + "getRParenLoc"); + + EXPECT_EQ(result.RangeAccessors.size(), 1u); + EXPECT_EQ(result.RangeAccessors.begin()->first, FooCall->getSourceRange()); + EXPECT_EQ(result.RangeAccessors.begin()->second, "getSourceRange"); +} +#endif