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