brintos

brintos / llvm-project-archived public Read only

0
0
Text · 14.6 KiB · 47b3093 Raw
420 lines · cpp
1//===-- lldb-rpc-gen.cpp ----------------------------------------*- C++ -*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "RPCCommon.h"10#include "server/RPCServerHeaderEmitter.h"11#include "server/RPCServerSourceEmitter.h"12 13#include "clang/AST/AST.h"14#include "clang/AST/ASTConsumer.h"15#include "clang/AST/ASTContext.h"16#include "clang/AST/RecursiveASTVisitor.h"17#include "clang/Basic/SourceManager.h"18#include "clang/CodeGen/ObjectFilePCHContainerWriter.h"19#include "clang/Frontend/CompilerInstance.h"20#include "clang/Frontend/FrontendAction.h"21#include "clang/Frontend/FrontendActions.h"22#include "clang/Serialization/ObjectFilePCHContainerReader.h"23#include "clang/Tooling/CommonOptionsParser.h"24#include "clang/Tooling/Tooling.h"25 26#include "llvm/ADT/StringRef.h"27#include "llvm/Support/CommandLine.h"28#include "llvm/Support/Path.h"29#include "llvm/Support/ToolOutputFile.h"30#include "llvm/Support/raw_ostream.h"31 32using namespace clang;33using namespace clang::driver;34using namespace clang::tooling;35 36static llvm::cl::OptionCategory RPCGenCategory("Tool for generating LLDBRPC");37 38static llvm::cl::opt<std::string>39    OutputDir("output-dir",40              llvm::cl::desc("Directory to output generated files to"),41              llvm::cl::init(""), llvm::cl::cat(RPCGenCategory));42 43static std::string GetServerOutputDirectory() {44  llvm::SmallString<128> Path(OutputDir.getValue());45  llvm::sys::path::append(Path, "server");46  return std::string(Path);47}48 49static std::unique_ptr<llvm::ToolOutputFile>50CreateOutputFile(llvm::StringRef OutputDir, llvm::StringRef Filename) {51  llvm::SmallString<256> Path(OutputDir);52  llvm::sys::path::append(Path, Filename);53 54  std::error_code EC;55  auto OutputFile =56      std::make_unique<llvm::ToolOutputFile>(Path, EC, llvm::sys::fs::OF_None);57  if (EC) {58    llvm::errs() << "Failed to create output file: " << Path << "!\n";59    return nullptr;60  }61  return OutputFile;62}63 64struct GeneratedByproducts {65  std::set<std::string> ClassNames;66  std::set<std::string> MangledMethodNames;67  std::set<std::string> SkippedMethodNames;68  std::set<lldb_rpc_gen::Method> CallbackMethods;69};70 71enum SupportLevel {72  eUnsupported,73  eUnimplemented,74  eImplemented,75};76 77class SBVisitor : public RecursiveASTVisitor<SBVisitor> {78public:79  SBVisitor(GeneratedByproducts &Byproducts, SourceManager &Manager,80            ASTContext &Context,81            std::unique_ptr<llvm::ToolOutputFile> &&ServerMethodOutputFile,82            std::unique_ptr<llvm::ToolOutputFile> &&ServerHeaderOutputFile)83      : Byproducts(Byproducts), Manager(Manager), Context(Context),84        ServerSourceEmitter(std::move(ServerMethodOutputFile)),85        ServerHeaderEmitter(std::move(ServerHeaderOutputFile)) {}86 87  ~SBVisitor() {}88 89  bool VisitCXXRecordDecl(CXXRecordDecl *RDecl) {90    if (ShouldSkipRecord(RDecl))91      return true;92 93    const std::string ClassName = RDecl->getNameAsString();94    Byproducts.ClassNames.insert(ClassName);95 96    // Print 'bool' instead of '_Bool'.97    PrintingPolicy Policy(Context.getLangOpts());98    Policy.Bool = true;99 100    for (CXXMethodDecl *MDecl : RDecl->methods()) {101      const std::string MangledName =102          lldb_rpc_gen::GetMangledName(Context, MDecl);103      const bool IsDisallowed =104          lldb_rpc_gen::MethodIsDisallowed(Context, MDecl);105      SupportLevel MethodSupportLevel = GetMethodSupportLevel(MDecl);106      if (MethodSupportLevel == eImplemented && !IsDisallowed) {107        const lldb_rpc_gen::Method Method(MDecl, Policy, Context);108        ServerSourceEmitter.EmitMethod(Method);109        ServerHeaderEmitter.EmitMethod(Method);110        Byproducts.MangledMethodNames.insert(MangledName);111      } else if (MethodSupportLevel == eUnimplemented)112        Byproducts.SkippedMethodNames.insert(MangledName);113    }114    return true;115  }116 117private:118  /// Determines whether we should skip a RecordDecl.119  /// Conditions for skipping:120  ///   - Anything not in the header itself121  ///   - Certain inconvenient classes122  ///   - Records without definitions (forward declarations)123  bool ShouldSkipRecord(CXXRecordDecl *Decl) {124    return !Manager.isInMainFile(Decl->getBeginLoc()) ||125           !Decl->hasDefinition() || Decl->getDefinition() != Decl ||126           lldb_rpc_gen::TypeIsDisallowedClass(127               Context.getCanonicalTagType(Decl));128  }129 130  /// Check the support level for a type131  /// Known unsupported types:132  ///  - FILE * (We do not want to expose this primitive)133  ///  - Types that are internal to LLDB134  SupportLevel GetTypeSupportLevel(QualType Type) {135    const std::string TypeName = Type.getAsString();136    if (TypeName == "FILE *" || lldb_rpc_gen::TypeIsFromLLDBPrivate(Type))137      return eUnsupported;138 139    if (lldb_rpc_gen::TypeIsDisallowedClass(Type))140      return eUnsupported;141 142    return eImplemented;143  }144 145  /// Determine the support level of a given method.146  /// Known unsupported methods:147  ///   - Non-public methods (lldb-rpc is a client and can only see public148  ///     things)149  ///   - Copy assignment operators (the client side will handle this)150  ///   - Move assignment operators (the client side will handle this)151  ///   - Methods involving unsupported types.152  /// Known unimplemented methods:153  ///   - No variadic functions, e.g. Printf154  SupportLevel GetMethodSupportLevel(CXXMethodDecl *MDecl) {155    AccessSpecifier AS = MDecl->getAccess();156    if (AS != AccessSpecifier::AS_public)157      return eUnsupported;158    if (MDecl->isCopyAssignmentOperator())159      return eUnsupported;160    if (MDecl->isMoveAssignmentOperator())161      return eUnsupported;162 163    if (MDecl->isVariadic())164      return eUnimplemented;165 166    SupportLevel ReturnTypeLevel = GetTypeSupportLevel(MDecl->getReturnType());167    if (ReturnTypeLevel != eImplemented)168      return ReturnTypeLevel;169 170    for (auto *ParamDecl : MDecl->parameters()) {171      SupportLevel ParamTypeLevel = GetTypeSupportLevel(ParamDecl->getType());172      if (ParamTypeLevel != eImplemented)173        return ParamTypeLevel;174    }175 176    // FIXME: If a callback does not take a `void *baton` parameter, it is177    // considered unsupported at this time. On the server-side, we hijack the178    // baton argument in order to pass additional information to the server-side179    // callback so we can correctly perform a reverse RPC call back to the180    // client. Without this baton, we would need the server-side callback to181    // have some side channel by which it obtained that information, and182    // spending time designing that doesn't outweight the cost of doing it at183    // the moment.184    bool HasCallbackParameter = false;185    bool HasBatonParameter = false;186    auto End = MDecl->parameters().end();187    for (auto Iter = MDecl->parameters().begin(); Iter != End; Iter++) {188      if ((*Iter)->getType()->isFunctionPointerType()) {189        HasCallbackParameter = true;190        continue;191      }192 193      // FIXME: We assume that if we have a function pointer and a void pointer194      // together in the same parameter list, that it is not followed by a195      // length argument. If that changes, we will need to revisit this196      // implementation.197      if ((*Iter)->getType()->isVoidPointerType())198        HasBatonParameter = true;199    }200 201    if (HasCallbackParameter && !HasBatonParameter)202      return eUnimplemented;203 204    return eImplemented;205  }206 207  GeneratedByproducts &Byproducts;208  SourceManager &Manager;209  ASTContext &Context;210  lldb_rpc_gen::RPCServerSourceEmitter ServerSourceEmitter;211  lldb_rpc_gen::RPCServerHeaderEmitter ServerHeaderEmitter;212};213 214class SBConsumer : public ASTConsumer {215public:216  SBConsumer(GeneratedByproducts &Byproducts, SourceManager &Manager,217             ASTContext &Context,218             std::unique_ptr<llvm::ToolOutputFile> &&ServerMethodOutputFile,219             std::unique_ptr<llvm::ToolOutputFile> &&ServerHeaderOutputFile)220      : Visitor(Byproducts, Manager, Context, std::move(ServerMethodOutputFile),221                std::move(ServerHeaderOutputFile)) {}222  bool HandleTopLevelDecl(DeclGroupRef DR) override {223    for (Decl *D : DR)224      Visitor.TraverseDecl(D);225 226    return true;227  }228 229private:230  SBVisitor Visitor;231};232 233class SBAction : public ASTFrontendAction {234public:235  SBAction(GeneratedByproducts &Byproducts) : Byproducts(Byproducts) {}236 237  std::unique_ptr<ASTConsumer>238  CreateASTConsumer(CompilerInstance &CI, llvm::StringRef File) override {239    llvm::StringRef FilenameNoExt =240        llvm::sys::path::stem(llvm::sys::path::filename(File));241 242    const std::string ServerMethodFilename =243        "Server_" + FilenameNoExt.str() + ".cpp";244    std::unique_ptr<llvm::ToolOutputFile> ServerMethodOutputFile =245        CreateOutputFile(GetServerOutputDirectory(), ServerMethodFilename);246    if (!ServerMethodOutputFile)247      return nullptr;248 249    const std::string ServerHeaderFilename =250        "Server_" + FilenameNoExt.str() + ".h";251    std::unique_ptr<llvm::ToolOutputFile> ServerHeaderOutputFile =252        CreateOutputFile(GetServerOutputDirectory(), ServerHeaderFilename);253    if (!ServerHeaderOutputFile)254      return nullptr;255 256    ServerMethodOutputFile->keep();257    ServerHeaderOutputFile->keep();258    return std::make_unique<SBConsumer>(259        Byproducts, CI.getSourceManager(), CI.getASTContext(),260        std::move(ServerMethodOutputFile), std::move(ServerHeaderOutputFile));261  }262 263private:264  GeneratedByproducts &Byproducts;265};266 267class SBActionFactory : public FrontendActionFactory {268public:269  SBActionFactory(GeneratedByproducts &Byproducts) : Byproducts(Byproducts) {}270 271  std::unique_ptr<FrontendAction> create() override {272    return std::make_unique<SBAction>(Byproducts);273  }274 275private:276  GeneratedByproducts &Byproducts;277};278 279bool EmitAmalgamatedServerHeader(const std::vector<std::string> &Files) {280  // Create the file281  static constexpr llvm::StringLiteral AmalgamatedServerHeaderName = "SBAPI.h";282  std::unique_ptr<llvm::ToolOutputFile> AmalgamatedServerHeader =283      CreateOutputFile(GetServerOutputDirectory(), AmalgamatedServerHeaderName);284  if (!AmalgamatedServerHeader)285    return false;286 287  // Write the header288  AmalgamatedServerHeader->os()289      << "#ifndef GENERATED_LLDB_RPC_SERVER_SBAPI_H\n";290  AmalgamatedServerHeader->os()291      << "#define GENERATED_LLDB_RPC_SERVER_SBAPI_H\n";292  for (const auto &File : Files) {293    llvm::StringRef FilenameNoExt =294        llvm::sys::path::stem(llvm::sys::path::filename(File));295    const std::string ServerHeaderFilename =296        "Server_" + FilenameNoExt.str() + ".h";297 298    AmalgamatedServerHeader->os()299        << "#include \"" + ServerHeaderFilename + "\"\n";300  }301  AmalgamatedServerHeader->os() << "#include \"SBAPIExtensions.h\"\n";302  AmalgamatedServerHeader->os()303      << "#endif // GENERATED_LLDB_RPC_SERVER_SBAPI_H\n";304  AmalgamatedServerHeader->keep();305  return true;306}307 308bool EmitClassNamesFile(std::set<std::string> &ClassNames) {309  static constexpr llvm::StringLiteral ClassNamesFileName = "SBClasses.def";310  std::unique_ptr<llvm::ToolOutputFile> ClassNamesFile =311      CreateOutputFile(OutputDir.getValue(), ClassNamesFileName);312  if (!ClassNamesFile)313    return false;314 315  ClassNamesFile->os() << "#ifndef SBCLASS\n"316                       << "#error \"SBClass must be defined\"\n"317                       << "#endif\n";318 319  for (const auto &ClassName : ClassNames) {320    if (ClassName == "SBStream" || ClassName == "SBProgress")321      ClassNamesFile->os() << "#if !defined(SBCLASS_EXCLUDE_NONCOPYABLE)\n";322    else if (ClassName == "SBReproducer")323      ClassNamesFile->os() << "#if !defined(SBCLASS_EXCLUDE_STATICONLY)\n";324 325    ClassNamesFile->os() << "SBCLASS(" << ClassName << ")\n";326    if (ClassName == "SBStream" || ClassName == "SBReproducer" ||327        ClassName == "SBProgress")328      ClassNamesFile->os() << "#endif\n";329  }330  ClassNamesFile->keep();331  return true;332}333 334bool EmitMethodNamesFile(std::set<std::string> &MangledMethodNames) {335  static constexpr llvm::StringLiteral MethodNamesFileName = "SBAPI.def";336  std::unique_ptr<llvm::ToolOutputFile> MethodNamesFile =337      CreateOutputFile(OutputDir.getValue(), MethodNamesFileName);338  if (!MethodNamesFile)339    return false;340 341  MethodNamesFile->os() << "#ifndef GENERATE_SBAPI\n"342                        << "#error \"GENERATE_SBAPI must be defined\"\n"343                        << "#endif\n";344 345  for (const auto &MangledName : MangledMethodNames) {346    MethodNamesFile->os() << "GENERATE_SBAPI(" << MangledName << ")\n";347  }348  MethodNamesFile->keep();349  return true;350}351 352bool EmitSkippedMethodsFile(std::set<std::string> &SkippedMethodNames) {353  static constexpr llvm::StringLiteral FileName = "SkippedMethods.txt";354  std::unique_ptr<llvm::ToolOutputFile> File =355      CreateOutputFile(OutputDir.getValue(), FileName);356  if (!File)357    return false;358 359  for (const auto &Skipped : SkippedMethodNames)360    File->os() << Skipped << "\n";361  File->keep();362  return true;363}364 365int main(int argc, const char *argv[]) {366  auto ExpectedParser = CommonOptionsParser::create(367      argc, argv, RPCGenCategory, llvm::cl::OneOrMore,368      "Tool for generating LLDBRPC interfaces and implementations");369 370  if (!ExpectedParser) {371    llvm::errs() << ExpectedParser.takeError();372    return 1;373  }374 375  if (OutputDir.empty()) {376    llvm::errs() << "Please specify an output directory for the generated "377                    "files with --output-dir!\n";378    return 1;379  }380 381  // Create the output directory if the user specified one does not exist.382  if (!llvm::sys::fs::exists(OutputDir.getValue())) {383    llvm::sys::fs::create_directory(OutputDir.getValue());384  }385 386  if (!llvm::sys::fs::exists(GetServerOutputDirectory())) {387    llvm::sys::fs::create_directory(GetServerOutputDirectory());388  }389  CommonOptionsParser &OP = ExpectedParser.get();390  auto PCHOpts = std::make_shared<PCHContainerOperations>();391  PCHOpts->registerWriter(std::make_unique<ObjectFilePCHContainerWriter>());392  PCHOpts->registerReader(std::make_unique<ObjectFilePCHContainerReader>());393 394  ClangTool T(OP.getCompilations(), OP.getSourcePathList(), PCHOpts);395 396  if (!EmitAmalgamatedServerHeader(OP.getSourcePathList())) {397    llvm::errs() << "Failed to create amalgamated server header\n";398    return 1;399  }400 401  GeneratedByproducts Byproducts;402 403  SBActionFactory Factory(Byproducts);404  auto Result = T.run(&Factory);405  if (!EmitClassNamesFile(Byproducts.ClassNames)) {406    llvm::errs() << "Failed to create SB Class file\n";407    return 1;408  }409  if (!EmitMethodNamesFile(Byproducts.MangledMethodNames)) {410    llvm::errs() << "Failed to create Method Names file\n";411    return 1;412  }413  if (!EmitSkippedMethodsFile(Byproducts.SkippedMethodNames)) {414    llvm::errs() << "Failed to create Skipped Methods file\n";415    return 1;416  }417 418  return Result;419}420