diff --git a/clang/include/clang/Tooling/NodeIntrospection.h b/clang/include/clang/Tooling/NodeIntrospection.h new file mode 100644 --- /dev/null +++ b/clang/include/clang/Tooling/NodeIntrospection.h @@ -0,0 +1,85 @@ +//===- NodeIntrospection.h ------------------------------------*- C++ -*---===// +// +// 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 contains the implementation of the NodeIntrospection. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLING_NODEINTROSPECTION_H +#define LLVM_CLANG_TOOLING_NODEINTROSPECTION_H + +#include "clang/AST/ASTTypeTraits.h" +#include "clang/AST/DeclarationName.h" + +#include +#include + +namespace clang { + +class Stmt; + +namespace tooling { + +class LocationCall { +public: + enum LocationCallFlags { NoFlags, ReturnsPointer, IsCast }; + LocationCall(std::shared_ptr on, std::string name, + LocationCallFlags flags = NoFlags) + : m_on(on), m_name(name), m_flags(flags) {} + LocationCall(std::shared_ptr on, std::string name, + std::vector const &args, + LocationCallFlags flags = NoFlags) + : m_on(on), m_name(name), m_flags(flags) {} + + LocationCall *on() const { return m_on.get(); } + StringRef name() const { return m_name; } + std::vector const &args() const { return m_args; } + bool returnsPointer() const { return m_flags & ReturnsPointer; } + bool isCast() const { return m_flags & IsCast; } + +private: + std::shared_ptr m_on; + std::string m_name; + std::vector m_args; + LocationCallFlags m_flags; +}; + +class LocationCallFormatterCpp { +public: + static std::string format(LocationCall *Call); +}; + +namespace internal { +struct RangeLessThan { + bool operator()( + std::pair> const &LHS, + std::pair> const &RHS) const; +}; +} // namespace internal + +template >> +using UniqueMultiMap = std::set, Comp>; + +using SourceLocationMap = + UniqueMultiMap>; +using SourceRangeMap = + UniqueMultiMap, + internal::RangeLessThan>; + +struct NodeLocationAccessors { + SourceLocationMap LocationAccessors; + SourceRangeMap RangeAccessors; +}; + +namespace NodeIntrospection { +NodeLocationAccessors GetLocations(clang::Stmt const *Object); +NodeLocationAccessors GetLocations(clang::DynTypedNode const &Node); +} // namespace NodeIntrospection +} // namespace tooling +} // namespace clang +#endif 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,78 @@ 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) + +# The generation of ASTNodeAPI.json takes a long time in a +# Debug build due to parsing AST.h. Disable the processing +# but setting CLANG_TOOLING_BUILD_AST_INTROSPECTION as an +# internal hidden setting to override. +# When the processing is disabled, a trivial/empty JSON +# file is generated by clang-ast-dump and generate_cxx_src_locs.py +# generates the same API, but with a trivial implementation. + +option(CLANG_TOOLING_BUILD_AST_INTROSPECTION "Enable AST introspection" TRUE) +set(skip_expensive_processing $,$>>) + +add_custom_command( + COMMENT Generate ASTNodeAPI.json + OUTPUT ${CMAKE_BINARY_DIR}/ASTNodeAPI.json + DEPENDS clang-ast-dump clang-headers + COMMAND + $ + # Skip this in debug mode because parsing AST.h is too slow + --skip-processing=${skip_expensive_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 +) + +# Replace the last lib component of the current binary directory with include +string(FIND ${CMAKE_CURRENT_BINARY_DIR} "/lib/" PATH_LIB_START REVERSE) +if(PATH_LIB_START EQUAL -1) + message(FATAL_ERROR "Couldn't find lib component in binary directory") +endif() +math(EXPR PATH_LIB_END "${PATH_LIB_START}+5") +string(SUBSTRING ${CMAKE_CURRENT_BINARY_DIR} 0 ${PATH_LIB_START} PATH_HEAD) +string(SUBSTRING ${CMAKE_CURRENT_BINARY_DIR} ${PATH_LIB_END} -1 PATH_TAIL) +string(CONCAT BINARY_INCLUDE_DIR ${PATH_HEAD} "/include/clang/" ${PATH_TAIL}) + +add_custom_command( + COMMENT Generate NodeIntrospection.inc + OUTPUT ${BINARY_INCLUDE_DIR}/NodeIntrospection.inc + DEPENDS ${CMAKE_BINARY_DIR}/ASTNodeAPI.json ${CMAKE_CURRENT_SOURCE_DIR}/DumpTool/generate_cxx_src_locs.py + COMMAND + ${CMAKE_COMMAND} -E make_directory + ${CMAKE_CURRENT_BINARY_DIR}/generated/ + COMMAND + ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/DumpTool/generate_cxx_src_locs.py + --json-input-path ${CMAKE_BINARY_DIR}/ASTNodeAPI.json + --output-file generated/NodeIntrospection.inc + --empty-implementation ${skip_expensive_processing} + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_BINARY_DIR}/generated/NodeIntrospection.inc + ${BINARY_INCLUDE_DIR}/NodeIntrospection.inc +) + +add_custom_target(run-ast-api-generate-tool + DEPENDS + ${BINARY_INCLUDE_DIR}/NodeIntrospection.inc +) + add_clang_library(clangTooling AllTUsExecution.cpp ArgumentsAdjusters.cpp @@ -27,6 +95,8 @@ Refactoring.cpp RefactoringCallbacks.cpp StandaloneExecution.cpp + NodeIntrospection.cpp + ${BINARY_INCLUDE_DIR}/NodeIntrospection.inc Tooling.cpp DEPENDS 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,33 @@ +//===- APIData.h ---------------------------------------------*- C++ -*----===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_TOOLING_DUMPTOOL_APIDATA_H +#define LLVM_CLANG_LIB_TOOLING_DUMPTOOL_APIDATA_H + +#include +#include + +namespace clang { +namespace tooling { + +struct ClassData { + + bool isEmpty() const { + return ASTClassLocations.empty() && ASTClassRanges.empty(); + } + + std::vector ASTClassLocations; + std::vector ASTClassRanges; + // 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 ---------------------------------*- C++ -*-----===// +// +// 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_DUMPTOOL_ASTSRCLOCPROCESSOR_H +#define LLVM_CLANG_TOOLING_DUMPTOOL_ASTSRCLOCPROCESSOR_H + +#include "APIData.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include + +namespace clang { + +class CompilerInstance; + +namespace tooling { + +class ASTSrcLocProcessor : public ast_matchers::MatchFinder::MatchCallback { +public: + explicit 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; + std::unique_ptr Finder; +}; + +} // 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,171 @@ +//===- ASTSrcLocProcessor.cpp --------------------------------*- C++ -*----===// +// +// 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) { + + MatchFinder::MatchFinderOptions FinderOptions; + + 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); +} + +std::unique_ptr +ASTSrcLocProcessor::createASTConsumer(clang::CompilerInstance &Compiler, + StringRef File) { + return Finder->newASTConsumer(); +} + +llvm::json::Object toJSON(llvm::StringMap> const &Obj) { + using llvm::json::toJSON; + + llvm::json::Object JsonObj; + for (const auto &Item : Obj) { + JsonObj[Item.first()] = Item.second; + } + return JsonObj; +} + +llvm::json::Object toJSON(llvm::StringMap const &Obj) { + using llvm::json::toJSON; + + llvm::json::Object JsonObj; + for (const auto &Item : Obj) { + JsonObj[Item.first()] = Item.second; + } + return JsonObj; +} + +llvm::json::Object toJSON(ClassData const &Obj) { + llvm::json::Object JsonObj; + + if (!Obj.ASTClassLocations.empty()) + JsonObj["sourceLocations"] = Obj.ASTClassLocations; + if (!Obj.ASTClassRanges.empty()) + JsonObj["sourceRanges"] = Obj.ASTClassRanges; + return JsonObj; +} + +llvm::json::Object 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 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 publicAccessor = [](auto... InnerMatcher) { + return cxxMethodDecl(isPublic(), parameterCountIs(0), isConst(), + InnerMatcher...); + }; + + 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; + + const auto *NodeClade = + Result.Nodes.getNodeAs("nodeClade"); + StringRef CladeName = NodeClade->getName(); + + if (const auto *DerivedFrom = + Result.Nodes.getNodeAs("derivedFrom")) + ClassInheritance[ClassName] = DerivedFrom->getName(); + + CD.ASTClassLocations = + CaptureMethods("class clang::SourceLocation", ASTClass, Result); + CD.ASTClassRanges = + 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,19 @@ + +add_clang_executable(clang-ast-dump + ASTSrcLocProcessor.cpp + ClangSrcLocDump.cpp +) + +target_link_libraries(clang-ast-dump + PRIVATE + clangAST + clangASTMatchers + clangBasic + clangDriver + clangFormat + clangFrontend + clangLex + clangRewrite + clangSerialization + 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,139 @@ +//===- ClangSrcLocDump.cpp ------------------------------------*- C++ -*---===// +// +// 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"); + + llvm::transform(IncludeDirectories, std::back_inserter(Args), + [](const std::string &IncDir) { return "-I" + IncDir; }); + + Args.push_back(AstHeaderFile); + + std::vector Argv(Args.size(), nullptr); + llvm::transform(Args, Argv.begin(), + [](const std::string &Arg) { return Arg.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()); + + auto Comp = Driver->BuildCompilation(llvm::makeArrayRef(Argv)); + if (!Comp) + return 1; + + const auto &Jobs = Comp->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,208 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import os +import sys +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('--output-file', help='Generate output in FILEPATH', + metavar='FILEPATH') +parser.add_option('--empty-implementation', help='Generate empty implementation', + action="store", type="int", metavar='FILEPATH') + +(options, args) = parser.parse_args() + +if options.empty_implementation: + with open(os.path.join(os.getcwd(), + options.output_file), 'w') as f: + f.write(""" +namespace clang { +namespace tooling { + +NodeLocationAccessors NodeIntrospection::GetLocations(clang::Stmt const *) { + return {}; +} +NodeLocationAccessors +NodeIntrospection::GetLocations(clang::DynTypedNode const &) { + return {}; +} +} // namespace tooling +} // namespace clang +""") + sys.exit(0) + +with open(options.json_input_path) as f: + jsonData = json.load(f) + + +class Generator(object): + + implementationContent = '' + + def GeneratePrologue(self): + + self.implementationContent += \ + """ +/*===- Generated file -------------------------------------------*- C++ -*-===*\ +|* *| +|* Introspection of available AST node SourceLocations *| +|* *| +|* Automatically generated file, do not edit! *| +|* *| +\*===----------------------------------------------------------------------===*/ + +namespace clang { +namespace tooling { + +using LocationAndString = SourceLocationMap::value_type; +using RangeAndString = SourceRangeMap::value_type; +""" + + def GenerateBaseGetLocationsDeclaration(self, CladeName): + self.implementationContent += \ + """ +void GetLocationsImpl(std::shared_ptr const& Prefix, clang::{0} const *Object, SourceLocationMap &Locs, + SourceRangeMap &Rngs); +""".format(CladeName) + + def GenerateSrcLocMethod(self, ClassName, ClassData): + + self.implementationContent += \ + """ +static void GetLocations{0}(std::shared_ptr const& Prefix, + clang::{0} const &Object, + SourceLocationMap &Locs, SourceRangeMap &Rngs) +{{ +""".format(ClassName) + + if 'sourceLocations' in ClassData: + for locName in ClassData['sourceLocations']: + self.implementationContent += \ + """ + Locs.insert(LocationAndString(Object.{0}(), std::make_shared(Prefix, "{0}"))); +""".format(locName) + + self.implementationContent += '\n' + + if 'sourceRanges' in ClassData: + for rngName in ClassData['sourceRanges']: + self.implementationContent += \ + """ + Rngs.insert(RangeAndString(Object.{0}(), std::make_shared(Prefix, "{0}"))); +""".format(rngName) + + self.implementationContent += '\n' + + self.implementationContent += '}\n' + + def GenerateFiles(self, OutputFile): + with open(os.path.join(os.getcwd(), + OutputFile), 'w') as f: + f.write(self.implementationContent) + + def GenerateTrivialBaseGetLocationsFunction(self, CladeName): + MethodReturnType = 'NodeLocationAccessors' + + Signature = \ + 'GetLocations(clang::{0} const *Object)'.format(CladeName) + + self.implementationContent += \ + '{0} NodeIntrospection::{1} {{ return {{}}; }}'.format(MethodReturnType, + Signature) + + def GenerateBaseGetLocationsFunction(self, ASTClassNames, CladeName): + + MethodReturnType = 'NodeLocationAccessors' + + Signature = \ + 'GetLocations(clang::{0} const *Object)'.format(CladeName) + ImplSignature = \ + """ +GetLocationsImpl(std::shared_ptr const& Prefix, + clang::{0} const *Object, SourceLocationMap &Locs, + SourceRangeMap &Rngs) +""".format(CladeName) + + self.implementationContent += \ + 'void {0} {{ GetLocations{1}(Prefix, *Object, Locs, Rngs);'.format(ImplSignature, + CladeName) + + for ASTClassName in ASTClassNames: + if ASTClassName != CladeName: + self.implementationContent += \ + """ +if (auto Derived = llvm::dyn_cast(Object)) {{ + GetLocations{0}(Prefix, *Derived, Locs, Rngs); +}} +""".format(ASTClassName) + + self.implementationContent += '}' + + self.implementationContent += \ + """ +{0} NodeIntrospection::{1} {{ + NodeLocationAccessors Result; + std::shared_ptr Prefix; + + GetLocationsImpl(Prefix, Object, Result.LocationAccessors, + Result.RangeAccessors); +""".format(MethodReturnType, + Signature) + + self.implementationContent += 'return Result; }' + + def GenerateDynNodeVisitor(self, CladeNames): + MethodReturnType = 'NodeLocationAccessors' + + Signature = \ + 'GetLocations(clang::DynTypedNode const &Node)' + + self.implementationContent += MethodReturnType \ + + ' NodeIntrospection::' + 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.implementationContent += ''' + } +} +''' + + +g = Generator() + +g.GeneratePrologue() + +if 'classesInClade' in jsonData: + for (CladeName, ClassNameData) in jsonData['classesInClade'].items(): + g.GenerateBaseGetLocationsDeclaration(CladeName) + + for (ClassName, ClassAccessors) in jsonData['classEntries'].items(): + if ClassAccessors: + g.GenerateSrcLocMethod(ClassName, ClassAccessors) + + for (CladeName, ClassNameData) in jsonData['classesInClade'].items(): + g.GenerateBaseGetLocationsFunction(ClassNameData, CladeName) + + g.GenerateDynNodeVisitor(jsonData['classesInClade'].keys()) +else: + for CladeName in ['Stmt']: + g.GenerateTrivialBaseGetLocationsFunction(CladeName) + + g.GenerateDynNodeVisitor([]) + +g.GenerateEpilogue() + +g.GenerateFiles(options.output_file) diff --git a/clang/lib/Tooling/NodeIntrospection.cpp b/clang/lib/Tooling/NodeIntrospection.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Tooling/NodeIntrospection.cpp @@ -0,0 +1,61 @@ +//===- NodeIntrospection.h -----------------------------------*- C++ -*----===// +// +// 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 contains the implementation of the NodeIntrospection. +// +//===----------------------------------------------------------------------===// + +#include "clang/Tooling/NodeIntrospection.h" + +#include "clang/AST/AST.h" + +namespace clang { + +namespace tooling { + +std::string LocationCallFormatterCpp::format(LocationCall *Call) { + SmallVector vec; + while (Call) { + vec.push_back(Call); + Call = Call->on(); + } + std::string result; + for (auto *VecCall : llvm::reverse(llvm::makeArrayRef(vec).drop_front())) { + result += + (VecCall->name() + "()" + (VecCall->returnsPointer() ? "->" : ".")) + .str(); + } + result += (vec.back()->name() + "()").str(); + return result; +} + +namespace internal { +bool RangeLessThan::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->name() < RHS.second->name(); +} +} // namespace internal + +} // namespace tooling +} // namespace clang + +#include "clang/Tooling/NodeIntrospection.inc" diff --git a/clang/unittests/CMakeLists.txt b/clang/unittests/CMakeLists.txt --- a/clang/unittests/CMakeLists.txt +++ b/clang/unittests/CMakeLists.txt @@ -29,6 +29,7 @@ add_subdirectory(AST) add_subdirectory(CrossTU) add_subdirectory(Tooling) +add_subdirectory(Introspection) add_subdirectory(Format) add_subdirectory(Frontend) add_subdirectory(Rewrite) 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,26 @@ +set(LLVM_LINK_COMPONENTS + ${LLVM_TARGETS_TO_BUILD} + FrontendOpenMP + Support + ) + +add_clang_unittest(IntrospectionTests + IntrospectionTest.cpp + ) + +clang_target_link_libraries(IntrospectionTests + PRIVATE + clangAST + clangASTMatchers + clangTooling + clangBasic + clangSerialization + clangFrontend + ) +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,100 @@ +//===- unittest/Introspection/IntrospectionTest.cpp ----------*- C++ -*---===// +// +// 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/NodeIntrospection.h" +#include "clang/Tooling/Tooling.h" +#include "gmock/gmock-matchers.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace clang; +using namespace clang::ast_matchers; +using namespace clang::tooling; + +using ::testing::UnorderedElementsAre; +using ::testing::Pair; + +#if SKIP_INTROSPECTION_GENERATION + +TEST(Introspection, NonFatalAPI) { + auto AST = 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 = NodeIntrospection::GetLocations(FooCall); + + EXPECT_EQ(result.LocationAccessors.size(), 0); + EXPECT_EQ(result.RangeAccessors.size(), 0); +} + +#else + +TEST(Introspection, SourceLocations) { + auto AST = 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 = NodeIntrospection::GetLocations(FooCall); + + std::map ExpectedLocations; + llvm::transform(result.LocationAccessors, + std::inserter(ExpectedLocations, ExpectedLocations.end()), + [](const auto &Accessor) { + return std::make_pair( + LocationCallFormatterCpp::format(Accessor.second.get()), + Accessor.first); + }); + + EXPECT_THAT(ExpectedLocations, + UnorderedElementsAre( + Pair("getBeginLoc()", FooCall->getBeginLoc()), + Pair("getEndLoc()", FooCall->getEndLoc()), + Pair("getExprLoc()", FooCall->getExprLoc()), + Pair("getRParenLoc()", FooCall->getRParenLoc()))); + + std::map ExpectedRanges; + llvm::transform(result.RangeAccessors, + std::inserter(ExpectedRanges, ExpectedRanges.end()), + [](const auto &Accessor) { + return std::make_pair( + LocationCallFormatterCpp::format(Accessor.second.get()), + Accessor.first); + }); + + EXPECT_THAT(ExpectedRanges, + UnorderedElementsAre( + Pair("getSourceRange()", FooCall->getSourceRange()))); +} +#endif