diff --git a/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckArrayPointer.h b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckArrayPointer.h new file mode 100644 index 0000000000000..18ec92a976b8a --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckArrayPointer.h @@ -0,0 +1,20 @@ +#ifndef LLDB_API_SBRPC_CHECKARRAYPTR_H +#define LLDB_API_SBRPC_CHECKARRAYPTR_H + +#include +#include + +#include "lldb/API/SBDefines.h" + +namespace lldb { +class LLDB_API SBRPC_CHECKARRAYPTR { +public: + // Pointers to arrays followed by length must use a + // Bytes object constructed using that pointer and the sizeof() + // the array object. + int CheckArrayPtr(uint64_t *array, size_t array_len); + +}; // class SBRPC_CHECKARRAYPTR +} // namespace lldb + +#endif // LLDB_API_SBRPC_CHECKARRAYPTR_H diff --git a/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstCharPtrPtrWithLen.h b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstCharPtrPtrWithLen.h new file mode 100644 index 0000000000000..fa64492cf5aa7 --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstCharPtrPtrWithLen.h @@ -0,0 +1,19 @@ +#ifndef LLDB_API_SBRPC_CHECKCONSTCHARPTRPTRWITHLEN_H +#define LLDB_API_SBRPC_CHECKCONSTCHARPTRPTRWITHLEN_H + +#include +#include + +#include "lldb/API/SBDefines.h" + +namespace lldb { +class LLDB_API SBRPC_CHECKCONSTCHARPTRPTRWITHLEN { +public: + // const char ** followed by len must use a StringList + // when being encoded. + int CheckConstCharPtrPtrWithLen(const char **arg1, size_t len); + +}; // class SBRPC_CHECKCONSTCHARPTRPTRWITHLEN +} // namespace lldb + +#endif // LLDB_API_SBRPC_CHECKCONSTCHARPTRPTRWITHLEN_H diff --git a/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstSBRef.h b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstSBRef.h new file mode 100644 index 0000000000000..153fdad60a494 --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstSBRef.h @@ -0,0 +1,19 @@ +#ifndef LLDB_API_SBRPC_CHECKCONSTSBREF_H +#define LLDB_API_SBRPC_CHECKCONSTSBREF_H + +#include +#include + +#include "lldb/API/SBDefines.h" + +namespace lldb { +class LLDB_API SBRPC_CHECKCONSTSBREF { +public: + // Const references to SB classes should be encoded as usual without + // needing to create a new object with its own connection. + int CheckConstSBRef(const SBDebugger &debugger_ref); + +}; // class SBRPC_CHECKCONSTSBREF +} // namespace lldb + +#endif // LLDB_API_SBRPC_CHECKCONSTSBREF_H diff --git a/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckNonConstSBRef.h b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckNonConstSBRef.h new file mode 100644 index 0000000000000..90ee3d3a7fbd6 --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckNonConstSBRef.h @@ -0,0 +1,20 @@ +#ifndef LLDB_API_SBRPC_CHECKNONCONSTSBREF_H +#define LLDB_API_SBRPC_CHECKNONCONSTSBREF_H + +#include +#include + +#include "lldb/API/SBDefines.h" + +namespace lldb { +class LLDB_API SBRPC_CHECKNONCONSTSBREF { +public: + // Non-const references to SB classes will have new objects + // of that class constructed with the connection as the first parameter + // before being encoded if their existing connection is invalid. + int CheckNonConstSBRef(SBDebugger &debugger_ref); + +}; // class SBRPC_CHECKNONCONSTSBREF +} // namespace lldb + +#endif // LLDB_API_SBRPC_CHECKNONCONSTSBREF_H diff --git a/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckSBPointer.h b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckSBPointer.h new file mode 100644 index 0000000000000..d9b7efa820f53 --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckSBPointer.h @@ -0,0 +1,21 @@ +#ifndef LLDB_API_SBRPC_CHECKSBPTR_H +#define LLDB_API_SBRPC_CHECKSBPTR_H + +#include +#include + +#include "lldb/API/SBDefines.h" + +namespace lldb { +class LLDB_API SBRPC_CHECKSBPTR { +public: + // Pointers to SB objects must be checked to + // see if they're null. If so, then a new object of the given + // class must be created and encoded. Otherwise, the original + // parameter will be encoded. + int CheckSBPtr(SBDebugger *debugger_ptr); + +}; // class SBRPC_CHECKSBPTR +} // namespace lldb + +#endif // LLDB_API_SBRPC_CHECKSBPTR_H diff --git a/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckVoidPtr.h b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckVoidPtr.h new file mode 100644 index 0000000000000..fa6484fc8d7cd --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckVoidPtr.h @@ -0,0 +1,19 @@ +#ifndef LLDB_API_SBRPC_CHECKVOIDPTR_H +#define LLDB_API_SBRPC_CHECKVOIDPTR_H + +#include +#include + +#include "lldb/API/SBDefines.h" + +namespace lldb { +class LLDB_API SBRPC_CHECKVOIDPTR { +public: + // void * followed by length must use a Bytes object + // when being encoded. + int CheckVoidPtr(void *buf, size_t len); + +}; // class SBRPC_CHECKVOIDPTR +} // namespace lldb + +#endif // LLDB_API_SBRPC_CHECKVOIDPTR_H diff --git a/lldb/test/Shell/RPC/Generator/Tests/Client/CheckArrayPointer.test b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckArrayPointer.test new file mode 100644 index 0000000000000..1b58b389d612e --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckArrayPointer.test @@ -0,0 +1,11 @@ +RUN: mkdir -p %t/server +RUN: mkdir -p %t/lib +RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/CheckArrayPointer.h + +RUN: cat %t/lib/CheckArrayPointer.cpp | FileCheck %s + +# Pointers to arrays followed by length must use a +# Bytes object constructed using that pointer and the sizeof() +# the array object. +CHECK: lldb_rpc::SBRPC_CHECKARRAYPTR::CheckArrayPtr +CHECK: Bytes array_buffer(array, sizeof(uint64_t)); diff --git a/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstCharPtrPtrWithLen.test b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstCharPtrPtrWithLen.test new file mode 100644 index 0000000000000..84d18c95fa7d2 --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstCharPtrPtrWithLen.test @@ -0,0 +1,10 @@ +RUN: mkdir -p %t/server +RUN: mkdir -p %t/lib +RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/CheckConstCharPtrPtrWithLen.h + +RUN: cat %t/lib/CheckConstCharPtrPtrWithLen.cpp | FileCheck %s + +# const char ** followed by len must use a StringList +# when being encoded. +CHECK: lldb_rpc::SBRPC_CHECKCONSTCHARPTRPTRWITHLEN::CheckConstCharPtrPtrWithLen +CHECK: StringList arg1_list diff --git a/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstSBRef.test b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstSBRef.test new file mode 100644 index 0000000000000..9b10d1711a9bc --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstSBRef.test @@ -0,0 +1,14 @@ +RUN: mkdir -p %t/server +RUN: mkdir -p %t/lib +RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/CheckConstSBRef.h + +RUN: cat %t/lib/CheckConstSBRef.cpp | FileCheck %s + +# Const references to SB classes should be encoded as usual without +# needing to create a new object with its own connection. Here +# we're checking to make sure that the given SB object ref will get +# encoded immediately after the previous argument gets encoded without +# anything happening in between. +CHECK: lldb_rpc::SBRPC_CHECKCONSTSBREF::CheckConstSBRef +CHECK: RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, *this); +CHECK: RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, debugger_ref) diff --git a/lldb/test/Shell/RPC/Generator/Tests/Client/CheckNonConstSBRef.test b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckNonConstSBRef.test new file mode 100644 index 0000000000000..d396e927b46d2 --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckNonConstSBRef.test @@ -0,0 +1,13 @@ +RUN: mkdir -p %t/server +RUN: mkdir -p %t/lib +RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/CheckNonConstSBRef.h + +RUN: cat %t/lib/CheckNonConstSBRef.cpp | FileCheck %s + +# Non-const references to SB classes will have new objects +# of that class constructed with the connection as the first parameter +# before being encoded if their existing connection is invalid. +CHECK: lldb_rpc::SBRPC_CHECKNONCONSTSBREF::CheckNonConstSBRef +CHECK: if (connection_sp && !debugger_ref.ObjectRefIsValid()) +CHECK: debugger_ref = lldb_rpc::SBDebugger(connection_sp); +CHECK: RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, debugger_ref); diff --git a/lldb/test/Shell/RPC/Generator/Tests/Client/CheckSBPointer.test b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckSBPointer.test new file mode 100644 index 0000000000000..5f355138182a8 --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckSBPointer.test @@ -0,0 +1,15 @@ +RUN: mkdir -p %t/server +RUN: mkdir -p %t/lib +RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/CheckSBPointer.h + +RUN: cat %t/lib/CheckSBPointer.cpp | FileCheck %s + +# Pointers to SB objects must be checked to +# see if they're null. If so, then a new object of the given +# class must be created and encoded. Otherwise, the original +# parameter will be encoded. +CHECK: lldb_rpc::SBRPC_CHECKSBPTR::CheckSBPtr +CHECK: if (debugger_ptr) +CHECK: RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, *debugger_ptr); +CHECK: else +CHECK: RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, rpc::ObjectRef(ObjectRefGetConnectionID(), eClass_lldb_SBDebugger, LLDB_RPC_INVALID_OBJECT_ID)); diff --git a/lldb/test/Shell/RPC/Generator/Tests/Client/CheckVoidPtr.test b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckVoidPtr.test new file mode 100644 index 0000000000000..9907126a28fa6 --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckVoidPtr.test @@ -0,0 +1,10 @@ +RUN: mkdir -p %t/server +RUN: mkdir -p %t/lib +RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/CheckVoidPtr.h + +RUN: cat %t/lib/CheckVoidPtr.cpp | FileCheck %s + +# void * followed by length must use a Bytes object +# when being encoded. +CHECK: lldb_rpc::SBRPC_CHECKVOIDPTR::CheckVoidPtr +CHECK: Bytes buf_buffer(buf, len); diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.cpp b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.cpp new file mode 100644 index 0000000000000..9537b490856f1 --- /dev/null +++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.cpp @@ -0,0 +1,124 @@ +#include "RPCLibraryHeaderEmitter.h" +#include "RPCCommon.h" + +#include "clang/AST/AST.h" +#include "clang/AST/Mangle.h" +#include "clang/Frontend/CompilerInstance.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace lldb_rpc_gen; + +void RPCLibraryHeaderEmitter::StartClass(std::string ClassName) { + CurrentClass = std::move(ClassName); + std::string BaseClass = + lldb_rpc_gen::SBClassInheritsFromObjectRef(CurrentClass) + ? "ObjectRef" + : "LocalObjectRef"; + EmitLine("class " + CurrentClass + " : public rpc::" + BaseClass + " {"); + EmitLine("public:"); + IndentLevel++; + if (lldb_rpc_gen::SBClassRequiresDefaultCtor(CurrentClass)) + EmitLine(CurrentClass + "();"); + + // NOTE: There's currently only one RPC-specific extension that is actually + // used AFAICT. We can generalize this if we need more. + if (CurrentClass == "SBDebugger") + EmitLine("int SetIOFile(const char *path);"); +} + +void RPCLibraryHeaderEmitter::EndClass() { + if (lldb_rpc_gen::SBClassRequiresCopyCtorAssign(CurrentClass)) { + if (!CopyCtorEmitted) + EmitLine(CurrentClass + "(const lldb_rpc::" + CurrentClass + " &rhs);"); + if (!CopyAssignEmitted) + EmitLine(CurrentClass + " &operator=(const lldb_rpc::" + CurrentClass + + " &rhs);"); + } + if (!MoveCtorEmitted) + EmitLine(CurrentClass + "(lldb_rpc::" + CurrentClass + " &&rhs);"); + if (!MoveAssignEmitted) + EmitLine(CurrentClass + " &operator=(" + CurrentClass + " &&rhs);"); + + IndentLevel--; + EmitLine("}; // class " + CurrentClass); + CurrentClass.clear(); +} + +void RPCLibraryHeaderEmitter::EmitMethod(const Method &method) { + std::string DeclarationLine; + llvm::raw_string_ostream DeclarationLineStream(DeclarationLine); + + if (method.IsCopyCtor) + CopyCtorEmitted = true; + else if (method.IsCopyAssign) + CopyAssignEmitted = true; + else if (method.IsMoveCtor) + MoveCtorEmitted = true; + else if (method.IsMoveAssign) + MoveAssignEmitted = true; + + if (method.IsExplicitCtorOrConversionMethod) + DeclarationLineStream << "explicit "; + else if (!method.IsInstance) + DeclarationLineStream << "static "; + + if (!method.IsDtor && !method.IsConversionMethod && !method.IsCtor) + DeclarationLineStream << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace( + method.ReturnType.getAsString(method.Policy)) + << " "; + DeclarationLineStream << method.BaseName << "(" + << method.CreateParamListAsString( + eLibrary, /*IncludeDefaultValue = */ true) + << ")"; + if (method.IsConst) + DeclarationLineStream << " const"; + DeclarationLineStream << ";"; + + EmitLine(DeclarationLine); +} + +void RPCLibraryHeaderEmitter::EmitEnum(EnumDecl *E) { + // NOTE: All of the enumerations embedded in SB classes are currently + // anonymous and backed by an unsigned int. + EmitLine("enum : unsigned {"); + IndentLevel++; + for (const EnumConstantDecl *EC : E->enumerators()) { + std::string EnumValue = EC->getNameAsString(); + SmallString<16> ValueStr; + EC->getInitVal().toString(ValueStr); + EnumValue += " = " + ValueStr.str().str() + ", "; + EmitLine(EnumValue); + } + + IndentLevel--; + EmitLine("};"); +} + +std::string RPCLibraryHeaderEmitter::GetHeaderGuard() { + const std::string UpperFilenameNoExt = + llvm::sys::path::stem( + llvm::sys::path::filename(OutputFile->getFilename())) + .upper(); + return "GENERATED_LLDB_RPC_LIBRARY_" + UpperFilenameNoExt + "_H"; +} + +void RPCLibraryHeaderEmitter::Begin() { + const std::string HeaderGuard = GetHeaderGuard(); + EmitLine("#ifndef " + HeaderGuard); + EmitLine("#define " + HeaderGuard); + EmitLine(""); + EmitLine("#include "); + EmitLine("#include \"SBDefines.h\""); + EmitLine("#include \"LLDBRPC.h\""); + EmitLine(""); + EmitLine("namespace lldb_rpc {"); +} + +void RPCLibraryHeaderEmitter::End() { + EmitLine("} // namespace lldb_rpc"); + EmitLine("#endif // " + GetHeaderGuard()); +} diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.h b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.h new file mode 100644 index 0000000000000..b9f22585c7955 --- /dev/null +++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.h @@ -0,0 +1,44 @@ +#ifndef LLDB_RPC_GEN_RPCLIBRARYHEADEREMITTER_H +#define LLDB_RPC_GEN_RPCLIBRARYHEADEREMITTER_H + +#include "RPCCommon.h" + +#include "clang/AST/AST.h" +#include "llvm/Support/ToolOutputFile.h" + +using namespace clang; + +namespace lldb_rpc_gen { + +class RPCLibraryHeaderEmitter : public FileEmitter { +public: + RPCLibraryHeaderEmitter(std::unique_ptr &&OutputFile) + : FileEmitter(std::move(OutputFile)), CurrentClass() { + Begin(); + } + + ~RPCLibraryHeaderEmitter() { End(); } + + void StartClass(std::string ClassName); + + void EndClass(); + + void EmitMethod(const Method &method); + + void EmitEnum(EnumDecl *E); + +private: + std::string GetHeaderGuard(); + + void Begin(); + + void End(); + + std::string CurrentClass; + bool CopyCtorEmitted = false; + bool CopyAssignEmitted = false; + bool MoveCtorEmitted = false; + bool MoveAssignEmitted = false; +}; +} // namespace lldb_rpc_gen +#endif // LLDB_RPC_GEN_RPCLIBRARYHEADEREMITTER_H diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.cpp b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.cpp new file mode 100644 index 0000000000000..571fedc6edcce --- /dev/null +++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.cpp @@ -0,0 +1,542 @@ +#include "RPCLibrarySourceEmitter.h" +#include "RPCCommon.h" + +#include "clang/AST/AST.h" +#include "clang/Frontend/CompilerInstance.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/raw_ostream.h" +#include + +using namespace clang; +using namespace lldb_rpc_gen; + +static constexpr llvm::StringRef ReturnVariableName("__result"); + +// This map stores any method that needs custom logic with a struct that +// tells us where the logic needs to be inserted and what code needs to be +// inserted. The code here is stored as a raw string literal. +const llvm::StringMap + CustomLogicForMethods = { + {"_ZN4lldb10SBDebugger6CreateEbPFvPKcPvES3_", + {RPCLibrarySourceEmitter::CustomLogicLocation::eAfterDecode, R"code( + // Now source the .lldbinit files manually since we can't rely on the + // LLDB.framework on the other side to have special support for sourcing the right file + // since it would try to source "~/.lldbinit-lldb-rpc-server" followed by + // "~/.lldbinit". We want it to try "~.lldbinit-%s" where %s is the + // current program basename followed by "~/.lldbinit". + + if (source_init_files && __result.ObjectRefIsValid()) { + const char *program_basename = rpc::GetProgramBasename(); + if (program_basename) { + char init_path[PATH_MAX]; + snprintf(init_path, sizeof(init_path), "~/.lldbinit-%s", + program_basename); + lldb_rpc::SBFileSpec program_init_file(connection, init_path, true); + if (program_init_file.Exists()) { + char command_str[PATH_MAX]; + snprintf(command_str, sizeof(command_str), + "command source -s 1 -c 1 -e 0 '%s'", init_path); + __result.HandleCommand(command_str); + } else { + __result.HandleCommand("command source -s 1 -c 1 -e 0 '~/.lldbinit'"); + } + } + })code"}}, +}; + +static std::string GetLocalObjectRefCtor(const std::string &ClassName) { + return "rpc::LocalObjectRef(LLDB_RPC_INVALID_CONNECTION_ID, eClass_lldb_" + + ClassName + ", LLDB_RPC_INVALID_OBJECT_ID)"; +} + +static std::string GetObjectRefCtor(const std::string &ClassName) { + return "rpc::ObjectRef(LLDB_RPC_INVALID_CONNECTION_ID, eClass_lldb_" + + ClassName + ", LLDB_RPC_INVALID_OBJECT_ID)"; +} + +void RPCLibrarySourceEmitter::EmitCopyCtor() { + EmitLine("lldb_rpc::" + CurrentClass + "::" + CurrentClass + + "(const lldb_rpc::" + CurrentClass + " &rhs) = default;"); +} + +void RPCLibrarySourceEmitter::EmitCopyAssign() { + EmitLine("lldb_rpc::" + CurrentClass + " &lldb_rpc::" + CurrentClass + + "::operator=(const lldb_rpc::" + CurrentClass + " &rhs) = default;"); +} + +void RPCLibrarySourceEmitter::EmitMoveCtor() { + EmitLine("lldb_rpc::" + CurrentClass + "::" + CurrentClass + + "(lldb_rpc::" + CurrentClass + " &&rhs) = default;"); +} + +void RPCLibrarySourceEmitter::EmitMoveAssign() { + EmitLine("lldb_rpc::" + CurrentClass + " &lldb_rpc::" + CurrentClass + + "::operator=(lldb_rpc::" + CurrentClass + " &&rhs) = default;"); +} + +void RPCLibrarySourceEmitter::EmitMethod(const Method &method) { + if (method.IsCopyCtor) { + CopyCtorEmitted = true; + EmitCopyCtor(); + return; + } else if (method.IsCopyAssign) { + CopyAssignEmitted = true; + EmitCopyAssign(); + return; + } else if (method.IsMoveCtor) { + MoveCtorEmitted = true; + EmitMoveCtor(); + return; + } else if (method.IsMoveAssign) { + MoveAssignEmitted = true; + EmitMoveAssign(); + return; + } + + EmitCommentHeader(method); + EmitFunctionHeader(method); + EmitFunctionBody(method); + EmitFunctionFooter(); +} + +void RPCLibrarySourceEmitter::StartClass(std::string ClassName) { + CurrentClass = std::move(ClassName); + if (lldb_rpc_gen::SBClassRequiresDefaultCtor(CurrentClass)) { + std::string BaseClassCtor = + lldb_rpc_gen::SBClassInheritsFromObjectRef(CurrentClass) + ? GetObjectRefCtor(CurrentClass) + : GetLocalObjectRefCtor(CurrentClass); + EmitLine("lldb_rpc::" + CurrentClass + "::" + CurrentClass + + "() : " + BaseClassCtor + " {}"); + } +} + +void RPCLibrarySourceEmitter::EndClass() { + if (lldb_rpc_gen::SBClassRequiresCopyCtorAssign(CurrentClass)) { + if (!CopyCtorEmitted) + EmitCopyCtor(); + + if (!CopyAssignEmitted) + EmitCopyAssign(); + } + + if (!MoveCtorEmitted) + EmitMoveCtor(); + + if (!MoveAssignEmitted) + EmitMoveAssign(); + + CopyCtorEmitted = false; + CopyAssignEmitted = false; + MoveCtorEmitted = false; + MoveAssignEmitted = false; +} + +void RPCLibrarySourceEmitter::EmitCommentHeader(const Method &method) { + std::string CommentLine; + llvm::raw_string_ostream CommentStream(CommentLine); + + CommentStream << "// " + << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace( + method.QualifiedName) + << "(" << method.CreateParamListAsString(eLibrary) << ")"; + if (method.IsConst) + CommentStream << " const"; + + EmitLine("//-----------------------------------------------------------"); + EmitLine(CommentLine); + EmitLine("//-----------------------------------------------------------"); +} + +void RPCLibrarySourceEmitter::EmitFunctionHeader(const Method &method) { + std::string FunctionHeader; + llvm::raw_string_ostream FunctionHeaderStream(FunctionHeader); + + if (!method.IsDtor && !method.IsConversionMethod && !method.IsCtor) + FunctionHeaderStream << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace( + method.ReturnType.getAsString(method.Policy)) + << " "; + + FunctionHeaderStream << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace( + method.QualifiedName) + << "(" << method.CreateParamListAsString(eLibrary) + << ")"; + if (method.IsConst) + FunctionHeaderStream << " const"; + if (method.IsCtor) { + if (lldb_rpc_gen::SBClassInheritsFromObjectRef(method.BaseName)) + FunctionHeaderStream << " : " << GetObjectRefCtor(method.BaseName); + else + FunctionHeaderStream << " : " << GetLocalObjectRefCtor(method.BaseName); + } + FunctionHeaderStream << " {"; + + EmitLine(FunctionHeader); + IndentLevel++; +} + +void RPCLibrarySourceEmitter::EmitFunctionBody(const Method &method) { + // There's nothing to do for destructors. The LocalObjectRef destructor should + // handle everything for us. + if (method.IsDtor) + return; + + EmitLine("// 1) Perform setup"); + EmitFunctionSetup(method); + EmitLine("// 2) Send RPC call"); + EmitSendRPCCall(method); + EmitLine("// 3) Decode return values"); + EmitDecodeReturnValues(method); +} + +void RPCLibrarySourceEmitter::EmitFunctionFooter() { + IndentLevel--; + EmitLine("}"); +} + +void RPCLibrarySourceEmitter::EmitFunctionSetup(const Method &method) { + if (!method.ReturnType->isVoidType()) + EmitReturnValueStorage(method); + + EmitConnectionSetup(method); + + EmitLine("// RPC Communication setup"); + EmitLine("static RPCFunctionInfo g_func(\"" + method.MangledName + "\");"); + EmitLine("RPCStream send;"); + EmitLine("RPCStream response;"); + EmitLine("g_func.Encode(send);"); + + EmitEncodeParameters(method); + + if (CustomLogicForMethods.lookup(method.MangledName).Location == + CustomLogicLocation::eAfterSetup) + EmitCustomLogic(method); +} + +void RPCLibrarySourceEmitter::EmitReturnValueStorage(const Method &method) { + assert(!method.ReturnType->isVoidType() && + "Cannot emit return value storage when return type is 'void'"); + + EmitLine("// Storage for return value"); + std::string ReturnValueStorage; + llvm::raw_string_ostream ReturnValueStorageStream(ReturnValueStorage); + + std::string ReturnValueType; + if (lldb_rpc_gen::TypeIsConstCharPtr(method.ReturnType)) + ReturnValueStorageStream << "rpc_common::ConstCharPointer " + << ReturnVariableName << ";"; + else if (method.ReturnType->isPointerType()) + ReturnValueStorageStream << "Bytes " << ReturnVariableName << ";"; + else { + // We need to get the unqualified type because we don't want the return + // variable to be marked `const`. That would prevent us from changing it + // during the decoding step. + QualType UnqualifiedReturnType = method.ReturnType.getUnqualifiedType(); + ReturnValueStorageStream + << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace( + UnqualifiedReturnType.getAsString(method.Policy)) + << " " << ReturnVariableName << " = {};"; + } + EmitLine(ReturnValueStorage); +} + +void RPCLibrarySourceEmitter::EmitConnectionSetup(const Method &method) { + // Methods know if they require a connection parameter. We need to figure out + // which scenario we're in. + bool ConnectionDerived = false; + if (!method.RequiresConnectionParameter()) { + // If we have an instance method that is not a constructor, we have a valid + // connection from `this` via `ObjectRefGetConnectionSP()`. + if (!method.IsCtor && method.IsInstance) { + EmitLine("// Deriving connection from this."); + EmitLine("rpc_common::ConnectionSP connection_sp = " + "ObjectRefGetConnectionSP();"); + ConnectionDerived = true; + } + + // Otherewise, we try to derive it from an existing parameter. + if (!ConnectionDerived) + for (const auto &Param : method.Params) { + if (lldb_rpc_gen::TypeIsSBClass(Param.Type)) { + EmitLine("// Deriving connection from SB class parameter."); + std::string ConnectionLine = + "rpc_common::ConnectionSP connection_sp = " + Param.Name; + if (Param.Type->isPointerType()) + ConnectionLine += "->"; + else + ConnectionLine += "."; + ConnectionLine += "ObjectRefGetConnectionSP();"; + EmitLine(ConnectionLine); + ConnectionDerived = true; + break; + } + } + } else { + // This method requires a connection parameter. It will always be named + // "connection" and it will always come first in the parameter list. + EmitLine("// Using connection parameter."); + EmitLine( + "rpc_common::ConnectionSP connection_sp = connection.GetConnection();"); + ConnectionDerived = true; + } + + assert(ConnectionDerived && + "Unable to determine where method should derive connection from"); + + // NOTE: By this point, we should have already emitted the storage for the + // return value. + std::string FailureReturnExpression; + if (method.ReturnType->isPointerType()) + FailureReturnExpression = "nullptr"; + else if (!method.ReturnType->isVoidType()) + FailureReturnExpression = "__result"; + EmitLine("if (!connection_sp) return " + FailureReturnExpression + ";"); +} + +void RPCLibrarySourceEmitter::EmitEncodeParameters(const Method &method) { + // Encode parameters. + if (method.IsInstance && !method.IsCtor) + EmitLine("RPCValueEncoder(send, " + "rpc_common::RPCPacket::ValueType::Argument, *this);"); + + for (auto Iter = method.Params.begin(); Iter != method.Params.end(); Iter++) { + // SBTarget::BreakpointCreateByNames specifically uses an + // rpc_common::StringList when encoding the list of symbol names in the + // handwritten version. This allows it to account for null terminators and + // without it, Xcode crashes when calling this function. The else-if block + // below replaces params that have a pointer and length with a Bytes object. + // We can use the same logic in order to replace const char **s with + // StringLists + // + // rdar://146976130 + if (lldb_rpc_gen::TypeIsConstCharPtrPtr(Iter->Type) && + Iter->IsFollowedByLen) { + std::string StringListLine; + const std::string StringListName = Iter->Name + "_list"; + StringListLine = "StringList " + StringListName + "(" + Iter->Name + ", "; + Iter++; + StringListLine += Iter->Name + ");"; + Iter--; + EmitLine(StringListLine); + EmitLine( + "RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, " + + StringListName + ");"); + // When we have pointer parameters, in general the strategy is + // to create `Bytes` objects from them (basically a buffer with a size) + // and then move those over the wire. We're not moving the pointer itself, + // but the contents of memory being pointed to. There are a few exceptions + // to this: + // - If the type is `const char *` or `const char **`, those are handled + // specially and can be encoded directly. + // - If we have a function pointer, we move the pointer value directly. + // To do the callback from the server-side, we will need this pointer + // value to correctly invoke the function client-side. + // - If we have a baton (to support a callback), we need to move the + // pointer value directly. This is for the same reason as callbacks + // above. + // - If we have a pointer to an SB class, we just dereference it and + // encode it like a normal SB class object. + } else if (Iter->Type->isPointerType() && + !Iter->Type->isFunctionPointerType() && + (!Iter->Type->isVoidPointerType() || Iter->IsFollowedByLen) && + !lldb_rpc_gen::TypeIsConstCharPtr(Iter->Type) && + !lldb_rpc_gen::TypeIsConstCharPtrPtr(Iter->Type) && + !lldb_rpc_gen::TypeIsSBClass(Iter->Type)) { + // NOTE: We're aiming for this transformation: + // (TYPE *buf, SIZE len) -> Bytes(buf, sizeof(TYPE) * len) + // The `sizeof` portion is dropped when TYPE is `void`. When there is no + // length argument, we implicitly assume that `len` is 1. + const std::string BufferName = Iter->Name + "_buffer"; + QualType UnderlyingType = lldb_rpc_gen::GetUnderlyingType(Iter->Type); + std::string BufferLine = "Bytes " + BufferName + "(" + Iter->Name + ", "; + if (!Iter->Type->isVoidPointerType()) + BufferLine += "sizeof(" + + ReplaceLLDBNamespaceWithRPCNamespace( + UnderlyingType.getAsString(method.Policy)) + + ")"; + + if (Iter->IsFollowedByLen && !Iter->Type->isVoidPointerType()) + BufferLine += " * "; + + if (Iter->IsFollowedByLen) { + Iter++; + BufferLine += Iter->Name; + } + + BufferLine += ");"; + EmitLine(BufferLine); + EmitLine("RPCValueEncoder(send, " + "rpc_common::RPCPacket::ValueType::Argument, " + + BufferName + ");"); + } else if (lldb_rpc_gen::TypeIsSBClass(Iter->Type) && + Iter->Type->isPointerType()) { + // If we have a pointer to an SB class, the strategy is to check for + // nullptr. If we have a valid pointer, we just encode the actual SB class + // itself. Otherwise, we'll need to create a blank one and send that + // along. + // Note: Currently all methods that take SB class pointers are instance + // methods. This assertion will fail if that changes. + assert(method.IsInstance && + "Assumption that only instance methods have pointers to SB class " + "as parameters is no longer true. Please update this logic."); + EmitLine("if (" + Iter->Name + ")"); + IndentLevel++; + EmitLine("RPCValueEncoder(send, " + "rpc_common::RPCPacket::ValueType::Argument, *" + + Iter->Name + ");"); + IndentLevel--; + EmitLine("else"); + IndentLevel++; + // FIXME: change this logic, not everything can be an rpc::ObjectRef!!! + const std::string ClassIdentifier = + "eClass_lldb_" + lldb_rpc_gen::GetSBClassNameFromType(Iter->Type); + EmitLine( + "RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, " + "rpc::ObjectRef(ObjectRefGetConnectionID(), " + + ClassIdentifier + ", LLDB_RPC_INVALID_OBJECT_ID));"); + IndentLevel--; + } else { + const std::string CallbackCast = Iter->Type->isFunctionPointerType() + ? "(rpc_common::function_ptr_t)" + : ""; + + // NOTE: We're going to assume that SB objects are coming in with a valid + // connection and we'll assert if they don't have one. + if (lldb_rpc_gen::TypeIsSBClass(Iter->Type)) { + std::string TypeName = lldb_rpc_gen::GetSBClassNameFromType(Iter->Type); + EmitLine("assert(" + Iter->Name + + ".ObjectRefIsValid() && \"SB object refs must be valid before " + "encoding\");"); + } + + EmitLine( + "RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, " + + CallbackCast + Iter->Name + ");"); + } + } +} + +void RPCLibrarySourceEmitter::EmitSendRPCCall(const Method &method) { + EmitLine( + "if (!connection_sp->SendRPCCallAndWaitForResponse(send, response))"); + IndentLevel++; + if (method.ReturnType->isVoidType() || method.IsCtor) + EmitLine("return;"); + else if (method.ReturnType->isPointerType()) + EmitLine("return nullptr;"); + else + EmitLine("return __result;"); + IndentLevel--; + if (CustomLogicForMethods.lookup(method.MangledName).Location == + CustomLogicLocation::eAfterRPCCall) + EmitCustomLogic(method); +} + +void RPCLibrarySourceEmitter::EmitDecodeReturnValues(const Method &method) { + if (!method.ReturnType->isVoidType()) { + std::string DecodeReturnValueLine = + "RPCValueDecoder(response, " + "rpc_common::RPCPacket::ValueType::ReturnValue, " + + ReturnVariableName.str() + ");"; + EmitLine(DecodeReturnValueLine); + } else if (method.IsCtor) + EmitLine("RPCValueDecoder(response, " + "rpc_common::RPCPacket::ValueType::ReturnValue, *this);"); + + // Update mutable parameters (references and pointers) + for (auto Iter = method.Params.begin(); Iter != method.Params.end(); Iter++) { + // If what we have is not a reference type or a pointer type, we can safely + // skip this. + if (!Iter->Type->isReferenceType() && !Iter->Type->isPointerType()) + continue; + + // No need to update SB class instances on the client-side. + if (lldb_rpc_gen::TypeIsSBClass(Iter->Type)) + continue; + + // We skip over function pointers and their accompanying baton parameters. + if (Iter->Type->isFunctionPointerType() || + (Iter->Type->isVoidPointerType() && !Iter->IsFollowedByLen)) + continue; + + // We cannot update const-qualified parameters. + QualType UnderlyingType = lldb_rpc_gen::GetUnderlyingType(Iter->Type); + // This is specific to pointers, but we need to get to the innermost type to + // get the qualification. For references, this loop will never execute. + while (UnderlyingType->isPointerType()) + UnderlyingType = lldb_rpc_gen::GetUnderlyingType(UnderlyingType); + + if (UnderlyingType.isConstQualified()) + continue; + + if (Iter->Type->isReferenceType()) + EmitLine("RPCValueDecoder(response, " + "rpc_common::RPCPacket::ValueType::ReturnValue, " + + Iter->Name + ");"); + else { + assert(Iter->Type->isPointerType() && + "Mutable parameter is not reference or pointer!"); + const std::string &PointerParameterName = Iter->Name; + const std::string BufferName = PointerParameterName + "_buffer"; + std::string SizeExpression; + + // If we have a `void *` with a length parameter, we are counting bytes. + // No `sizeof` will be needed. + if (!Iter->Type->isVoidPointerType()) { + QualType UnderlyingType = lldb_rpc_gen::GetUnderlyingType(Iter->Type); + SizeExpression = "sizeof(" + + ReplaceLLDBNamespaceWithRPCNamespace( + UnderlyingType.getAsString(method.Policy)) + + ")"; + } + + if (Iter->IsFollowedByLen) { + // If we have a sizeof, we must multiply by the length argument. + if (!SizeExpression.empty()) + SizeExpression += " * "; + Iter++; + SizeExpression += Iter->Name; + } + + EmitLine("RPCValueDecoder(response, " + "rpc_common::RPCPacket::ValueType::ReturnValue, " + + BufferName + ");"); + EmitLine("assert(" + BufferName + ".GetSize() == " + SizeExpression + + " && \"Buffer was resized during RPC call\");"); + // NOTE: We can just treat the pointers as `void *` and copy all the bytes + // needed. + EmitLine("memcpy(" + PointerParameterName + ", " + BufferName + + ".GetData(), " + BufferName + ".GetSize());"); + } + } + + if (CustomLogicForMethods.lookup(method.MangledName).Location == + CustomLogicLocation::eAfterDecode) + EmitCustomLogic(method); + if (!method.ReturnType->isVoidType()) { + // FIXME: Find a solution that does not involve leaking memory. + // We have to persist the buffer we returned somewhere. We stick it in the + // RPCStringPool for now + if (method.ReturnType->isPointerType()) { + std::string ReturnExpression = + "return (" + + lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace( + method.ReturnType.getAsString(method.Policy)) + + ")RPCStringPool::Add(" + ReturnVariableName.str() + ");"; + EmitLine(ReturnExpression); + } else + EmitLine("return __result;"); + } +} + +void RPCLibrarySourceEmitter::EmitCustomLogic(const Method &method) { + assert(CustomLogicForMethods.contains(method.MangledName) && + "Cannot emit custom logic for method that is not present in custom " + "logic map."); + EmitLine("// Custom logic:"); + EmitLine(CustomLogicForMethods.lookup(method.MangledName).Code); +} diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.h b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.h new file mode 100644 index 0000000000000..be37c82f5f46e --- /dev/null +++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.h @@ -0,0 +1,92 @@ +#ifndef LLDB_RPC_GEN_RPCLIBRARYSOURCEEMITTER_H +#define LLDB_RPC_GEN_RPCLIBRARYSOURCEEMITTER_H + +#include "RPCCommon.h" + +#include "clang/AST/AST.h" + +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; + +namespace lldb_rpc_gen { +class RPCLibrarySourceEmitter : public FileEmitter { +public: + RPCLibrarySourceEmitter(std::unique_ptr &&OutputFile) + : FileEmitter(std::move(OutputFile)) { + Begin(); + } + + enum class CustomLogicLocation { + eNone, + eAfterSetup, + eAfterRPCCall, + eAfterDecode + }; + + struct CustomLogic { + CustomLogicLocation Location; + std::string Code; + }; + + void StartClass(std::string ClassName); + + void EndClass(); + + void EmitMethod(const Method &method); + + void EmitEmptyConstructor(const std::string &ClassName); + +private: + void EmitCopyCtor(); + + void EmitCopyAssign(); + + void EmitMoveCtor(); + + void EmitMoveAssign(); + + void EmitCommentHeader(const Method &method); + + void EmitFunctionHeader(const Method &method); + + void EmitFunctionBody(const Method &method); + + void EmitFunctionFooter(); + + void EmitFunctionSetup(const Method &method); + + void EmitReturnValueStorage(const Method &method); + + void EmitConnectionSetup(const Method &method); + + void EmitEncodeParameters(const Method &method); + + void EmitSendRPCCall(const Method &method); + + void EmitDecodeReturnValues(const Method &method); + + void EmitCustomLogic(const Method &method); + + void Begin() { + EmitLine("#include "); + EmitLine("#include "); + EmitLine("#include "); + EmitLine("#include "); + EmitLine("#include "); + EmitLine("#include \"LLDBRPC.h\""); + EmitLine("#include "); + EmitLine("using namespace rpc_common;"); + EmitLine("using namespace lldb_rpc;"); + } + + std::string CurrentClass; + bool CopyCtorEmitted = false; + bool CopyAssignEmitted = false; + bool MoveCtorEmitted = false; + bool MoveAssignEmitted = false; +}; +} // namespace lldb_rpc_gen + +#endif // LLDB_RPC_GEN_RPCLIBRARYSOURCEEMITTER_H diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp b/lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp new file mode 100644 index 0000000000000..92d0182c79beb --- /dev/null +++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp @@ -0,0 +1,414 @@ +//===-- lldb-rpc-gen.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 +// +//===----------------------------------------------------------------------===// + +#include "RPCCommon.h" +#include "RPCLibraryHeaderEmitter.h" +#include "RPCLibrarySourceEmitter.h" + +#include "clang/AST/AST.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/SourceManager.h" +#include "clang/CodeGen/ObjectFilePCHContainerWriter.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/FrontendActions.h" +#include "clang/Serialization/ObjectFilePCHContainerReader.h" +#include "clang/Tooling/CommonOptionsParser.h" +#include "clang/Tooling/Tooling.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace clang::driver; +using namespace clang::tooling; + +static llvm::cl::OptionCategory RPCGenCategory("Tool for generating LLDBRPC"); + +static llvm::cl::opt + OutputDir("output-dir", + llvm::cl::desc("Directory to output generated files to"), + llvm::cl::init(""), llvm::cl::cat(RPCGenCategory)); + +static std::string GetLibraryOutputDirectory() { + llvm::SmallString<128> Path(OutputDir.getValue()); + llvm::sys::path::append(Path, "lib"); + return std::string(Path); +} + +static std::unique_ptr +CreateOutputFile(llvm::StringRef OutputDir, llvm::StringRef Filename) { + llvm::SmallString<128> Path(OutputDir); + llvm::sys::path::append(Path, Filename); + + std::error_code EC; + auto OutputFile = + std::make_unique(Path, EC, llvm::sys::fs::OF_None); + if (EC) { + llvm::errs() << "Failed to create output file: " << Path << "!\n"; + return nullptr; + } + return OutputFile; +} + +struct GeneratedByproducts { + std::set ClassNames; + std::set MangledMethodNames; + std::set SkippedMethodNames; + std::set CallbackMethods; +}; + +enum SupportLevel { + eUnsupported, + eUnimplemented, + eImplemented, +}; + +class SBVisitor : public RecursiveASTVisitor { +public: + SBVisitor(GeneratedByproducts &Byproducts, SourceManager &Manager, + ASTContext &Context, + std::unique_ptr &&LibrarySourceOutputFile, + std::unique_ptr &&LibraryHeaderOutputFile) + : Byproducts(Byproducts), Manager(Manager), Context(Context), + LibrarySourceEmitter(std::move(LibrarySourceOutputFile)), + LibraryHeaderEmitter(std::move(LibraryHeaderOutputFile)) {} + + ~SBVisitor() {} + + bool VisitCXXRecordDecl(CXXRecordDecl *RDecl) { + if (ShouldSkipRecord(RDecl)) + return true; + + const std::string ClassName = RDecl->getNameAsString(); + Byproducts.ClassNames.insert(ClassName); + + // Print 'bool' instead of '_Bool'. + PrintingPolicy Policy(Context.getLangOpts()); + Policy.Bool = true; + + LibraryHeaderEmitter.StartClass(ClassName); + LibrarySourceEmitter.StartClass(ClassName); + for (Decl *D : RDecl->decls()) + if (auto *E = dyn_cast_or_null(D)) + LibraryHeaderEmitter.EmitEnum(E); + + for (CXXMethodDecl *MDecl : RDecl->methods()) { + const std::string MangledName = + lldb_rpc_gen::GetMangledName(Context, MDecl); + const bool IsDisallowed = lldb_rpc_gen::MethodIsDisallowed(MangledName); + const bool HasCallbackParameter = + lldb_rpc_gen::HasCallbackParameter(MDecl); + SupportLevel MethodSupportLevel = GetMethodSupportLevel(MDecl); + if (MethodSupportLevel == eImplemented && !IsDisallowed) { + const lldb_rpc_gen::Method Method(MDecl, Policy, Context); + LibrarySourceEmitter.EmitMethod(Method); + LibraryHeaderEmitter.EmitMethod(Method); + Byproducts.MangledMethodNames.insert(MangledName); + } else if (MethodSupportLevel == eUnimplemented) + Byproducts.SkippedMethodNames.insert(MangledName); + } + LibraryHeaderEmitter.EndClass(); + LibrarySourceEmitter.EndClass(); + return true; + } + +private: + /// Determines whether we should skip a RecordDecl. + /// Conditions for skipping: + /// - Anything not in the header itself + /// - Certain inconvenient classes + /// - Records without definitions (forward declarations) + bool ShouldSkipRecord(CXXRecordDecl *Decl) { + const Type *DeclType = Decl->getTypeForDecl(); + QualType CanonicalType = DeclType->getCanonicalTypeInternal(); + return !Manager.isInMainFile(Decl->getBeginLoc()) || + !Decl->hasDefinition() || Decl->getDefinition() != Decl || + lldb_rpc_gen::TypeIsDisallowedClass(CanonicalType); + } + + /// Check the support level for a type + /// Known unsupported types: + /// - FILE * (We do not want to expose this primitive) + /// - Types that are internal to LLDB + SupportLevel GetTypeSupportLevel(QualType Type) { + const std::string TypeName = Type.getAsString(); + if (TypeName == "FILE *" || lldb_rpc_gen::TypeIsFromLLDBPrivate(Type)) + return eUnsupported; + + if (lldb_rpc_gen::TypeIsDisallowedClass(Type)) + return eUnsupported; + + return eImplemented; + } + + /// Determine the support level of a given method. + /// Known unsupported methods: + /// - Non-public methods (lldb-rpc is a client and can only see public + /// things) + /// - Copy assignment operators (the client side will handle this) + /// - Move assignment operators (the client side will handle this) + /// - Methods involving unsupported types. + /// Known unimplemented methods: + /// - No variadic functions, e.g. Printf + SupportLevel GetMethodSupportLevel(CXXMethodDecl *MDecl) { + AccessSpecifier AS = MDecl->getAccess(); + if (AS != AccessSpecifier::AS_public) + return eUnsupported; + if (MDecl->isCopyAssignmentOperator()) + return eUnsupported; + if (MDecl->isMoveAssignmentOperator()) + return eUnsupported; + + if (MDecl->isVariadic()) + return eUnimplemented; + + SupportLevel ReturnTypeLevel = GetTypeSupportLevel(MDecl->getReturnType()); + if (ReturnTypeLevel != eImplemented) + return ReturnTypeLevel; + + for (auto *ParamDecl : MDecl->parameters()) { + SupportLevel ParamTypeLevel = GetTypeSupportLevel(ParamDecl->getType()); + if (ParamTypeLevel != eImplemented) + return ParamTypeLevel; + } + + // FIXME: If a callback does not take a `void *baton` parameter, it is + // considered unsupported at this time. On the server-side, we hijack the + // baton argument in order to pass additional information to the server-side + // callback so we can correctly perform a reverse RPC call back to the + // client. Without this baton, we would need the server-side callback to + // have some side channel by which it obtained that information, and + // spending time designing that doesn't outweight the cost of doing it at + // the moment. + bool HasCallbackParameter = false; + bool HasBatonParameter = false; + auto End = MDecl->parameters().end(); + for (auto Iter = MDecl->parameters().begin(); Iter != End; Iter++) { + if ((*Iter)->getType()->isFunctionPointerType()) { + HasCallbackParameter = true; + continue; + } + + // FIXME: We assume that if we have a function pointer and a void pointer + // together in the same parameter list, that it is not followed by a + // length argument. If that changes, we will need to revisit this + // implementation. + if ((*Iter)->getType()->isVoidPointerType()) + HasBatonParameter = true; + } + + if (HasCallbackParameter && !HasBatonParameter) + return eUnimplemented; + + return eImplemented; + } + + GeneratedByproducts &Byproducts; + SourceManager &Manager; + ASTContext &Context; + lldb_rpc_gen::RPCLibrarySourceEmitter LibrarySourceEmitter; + lldb_rpc_gen::RPCLibraryHeaderEmitter LibraryHeaderEmitter; +}; + +class SBConsumer : public ASTConsumer { +public: + SBConsumer(GeneratedByproducts &Byproducts, SourceManager &Manager, + ASTContext &Context, + std::unique_ptr &&LibrarySourceOutputFile, + std::unique_ptr &&LibraryHeaderOutputFile) + : Visitor(Byproducts, Manager, Context, + std::move(LibrarySourceOutputFile), + std::move(LibraryHeaderOutputFile), ) {} + bool HandleTopLevelDecl(DeclGroupRef DR) override { + for (Decl *D : DR) + Visitor.TraverseDecl(D); + + return true; + } + +private: + SBVisitor Visitor; +}; + +class SBAction : public ASTFrontendAction { +public: + SBAction(GeneratedByproducts &Byproducts) : Byproducts(Byproducts) {} + + std::unique_ptr + CreateASTConsumer(CompilerInstance &CI, llvm::StringRef File) override { + llvm::StringRef FilenameNoExt = + llvm::sys::path::stem(llvm::sys::path::filename(File)); + + const std::string LibrarySourceFilename = FilenameNoExt.str() + ".cpp"; + std::unique_ptr LibrarySourceOutputFile = + CreateOutputFile(GetLibraryOutputDirectory(), LibrarySourceFilename); + if (!LibrarySourceOutputFile) + return nullptr; + + const std::string LibraryHeaderFilename = FilenameNoExt.str() + ".h"; + std::unique_ptr LibraryHeaderOutputFile = + CreateOutputFile(GetLibraryOutputDirectory(), LibraryHeaderFilename); + if (!LibraryHeaderOutputFile) + return nullptr; + + LibrarySourceOutputFile->keep(); + LibraryHeaderOutputFile->keep(); + return std::make_unique( + Byproducts, CI.getSourceManager(), CI.getASTContext(), + std::move(LibrarySourceOutputFile), std::move(LibraryHeaderOutputFile)); + } + +private: + GeneratedByproducts &Byproducts; +}; + +class SBActionFactory : public FrontendActionFactory { +public: + SBActionFactory(GeneratedByproducts &Byproducts) : Byproducts(Byproducts) {} + + std::unique_ptr create() override { + return std::make_unique(Byproducts); + } + +private: + GeneratedByproducts &Byproducts; +}; + +bool EmitAmalgamatedLibraryHeader(const std::vector &Files) { + static constexpr llvm::StringLiteral AmalgamatedLibraryHeaderName = + "LLDBRPC.h"; + std::unique_ptr AmalgamatedLibraryHeader = + CreateOutputFile(GetLibraryOutputDirectory(), + AmalgamatedLibraryHeaderName); + if (!AmalgamatedLibraryHeader) + return false; + + AmalgamatedLibraryHeader->os() << "#ifndef LLDBRPC_H\n"; + AmalgamatedLibraryHeader->os() << "#define LLDBRPC_H\n"; + AmalgamatedLibraryHeader->os() << "#include \"SBLanguages.h\"\n"; + for (const auto &File : Files) { + llvm::StringRef Filename = llvm::sys::path::filename(File); + AmalgamatedLibraryHeader->os() << "#include \"" << Filename << "\"\n"; + } + + AmalgamatedLibraryHeader->os() << "#endif // LLDBRPC_H\n"; + AmalgamatedLibraryHeader->keep(); + return true; +} + +bool EmitClassNamesFile(std::set &ClassNames) { + static constexpr llvm::StringLiteral ClassNamesFileName = "SBClasses.def"; + std::unique_ptr ClassNamesFile = + CreateOutputFile(OutputDir.getValue(), ClassNamesFileName); + if (!ClassNamesFile) + return false; + + ClassNamesFile->os() << "#ifndef SBCLASS\n"; + ClassNamesFile->os() << "#error \"SBClass must be defined\"\n"; + ClassNamesFile->os() << "#endif\n"; + + for (const auto &ClassName : ClassNames) { + if (ClassName == "SBStream" || ClassName == "SBProgress") + ClassNamesFile->os() << "#if !defined(SBCLASS_EXCLUDE_NONCOPYABLE)\n"; + else if (ClassName == "SBReproducer") + ClassNamesFile->os() << "#if !defined(SBCLASS_EXCLUDE_STATICONLY)\n"; + + ClassNamesFile->os() << "SBCLASS(" << ClassName << ")\n"; + if (ClassName == "SBStream" || ClassName == "SBReproducer" || + ClassName == "SBProgress") + ClassNamesFile->os() << "#endif\n"; + } + ClassNamesFile->keep(); + return true; +} + +bool EmitMethodNamesFile(std::set &MangledMethodNames) { + static constexpr llvm::StringLiteral MethodNamesFileName = "SBAPI.def"; + std::unique_ptr MethodNamesFile = + CreateOutputFile(OutputDir.getValue(), MethodNamesFileName); + if (!MethodNamesFile) + return false; + + MethodNamesFile->os() << "#ifndef GENERATE_SBAPI\n"; + MethodNamesFile->os() << "#error \"GENERATE_SBAPI must be defined\"\n"; + MethodNamesFile->os() << "#endif\n"; + + for (const auto &MangledName : MangledMethodNames) { + MethodNamesFile->os() << "GENERATE_SBAPI(" << MangledName << ")\n"; + } + MethodNamesFile->keep(); + return true; +} + +bool EmitSkippedMethodsFile(std::set &SkippedMethodNames) { + static constexpr llvm::StringLiteral FileName = "SkippedMethods.txt"; + std::unique_ptr File = + CreateOutputFile(OutputDir.getValue(), FileName); + if (!File) + return false; + + for (const auto &Skipped : SkippedMethodNames) { + File->os() << Skipped << "\n"; + } + File->keep(); + return true; +} + +int main(int argc, const char *argv[]) { + auto ExpectedParser = CommonOptionsParser::create( + argc, argv, RPCGenCategory, llvm::cl::OneOrMore, + "Tool for generating LLDBRPC interfaces and implementations"); + + if (!ExpectedParser) { + llvm::errs() << ExpectedParser.takeError(); + return 1; + } + + if (OutputDir.empty()) { + llvm::errs() << "Please specify an output directory for the generated " + "files with --output-dir!\n"; + return 1; + } + + CommonOptionsParser &OP = ExpectedParser.get(); + auto PCHOpts = std::make_shared(); + PCHOpts->registerWriter(std::make_unique()); + PCHOpts->registerReader(std::make_unique()); + + ClangTool T(OP.getCompilations(), OP.getSourcePathList(), PCHOpts); + + if (!EmitAmalgamatedLibraryHeader(OP.getSourcePathList())) { + llvm::errs() << "Failed to create amalgamated library header\n"; + return 1; + } + + GeneratedByproducts Byproducts; + + SBActionFactory Factory(Byproducts); + auto Result = T.run(&Factory); + if (!EmitClassNamesFile(Byproducts.ClassNames)) { + llvm::errs() << "Failed to create SB Class file\n"; + return 1; + } + if (!EmitMethodNamesFile(Byproducts.MangledMethodNames)) { + llvm::errs() << "Failed to create Method Names file\n"; + return 1; + } + if (!EmitSkippedMethodsFile(Byproducts.SkippedMethodNames)) { + llvm::errs() << "Failed to create Skipped Methods file\n"; + return 1; + } + + return Result; +}