[Lldb-commits] [lldb] [lldb][rpc] Upstream RPC Client Library Emitters (PR #147655)
Chelsea Cassanova via lldb-commits
lldb-commits at lists.llvm.org
Fri Jul 18 08:59:31 PDT 2025
https://github.com/chelcassanova updated https://github.com/llvm/llvm-project/pull/147655
>From 515f1136fb31abd68f4f668beaab9eb65375803d Mon Sep 17 00:00:00 2001
From: Chelsea Cassanova <chelsea_cassanova at apple.com>
Date: Wed, 2 Jul 2025 00:17:31 -0700
Subject: [PATCH] [lldb][rpc] Upstream RPC Client Library Emitters
This commit upstreams the client side emitters for the lldb-rpc-gen tool
alongside tests for its functionality and heuristics.
https://discourse.llvm.org/t/rfc-upstreaming-lldb-rpc/85804
---
.../Inputs/Client/CheckArrayPointer.h | 17 +
.../Client/CheckConstCharPtrPtrWithLen.h | 19 +
.../Generator/Inputs/Client/CheckConstSBRef.h | 19 +
.../Inputs/Client/CheckNonConstSBRef.h | 19 +
.../Generator/Inputs/Client/CheckSBPointer.h | 18 +
.../Generator/Inputs/Client/CheckVoidPtr.h | 18 +
.../Tests/Client/CheckArrayPointer.test | 12 +
.../Client/CheckConstCharPtrPtrWithLen.test | 11 +
.../Tests/Client/CheckConstSBRef.test | 15 +
.../Tests/Client/CheckNonConstSBRef.test | 14 +
.../Tests/Client/CheckSBPointer.test | 17 +
.../Generator/Tests/Client/CheckVoidPtr.test | 11 +
.../client/RPCLibraryHeaderEmitter.cpp | 126 ++++
.../client/RPCLibraryHeaderEmitter.h | 44 ++
.../client/RPCLibrarySourceEmitter.cpp | 544 ++++++++++++++++++
.../client/RPCLibrarySourceEmitter.h | 92 +++
.../lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp | 99 +++-
17 files changed, 1087 insertions(+), 8 deletions(-)
create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/Client/CheckArrayPointer.h
create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstCharPtrPtrWithLen.h
create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/Client/CheckConstSBRef.h
create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/Client/CheckNonConstSBRef.h
create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/Client/CheckSBPointer.h
create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/Client/CheckVoidPtr.h
create mode 100644 lldb/test/Shell/RPC/Generator/Tests/Client/CheckArrayPointer.test
create mode 100644 lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstCharPtrPtrWithLen.test
create mode 100644 lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstSBRef.test
create mode 100644 lldb/test/Shell/RPC/Generator/Tests/Client/CheckNonConstSBRef.test
create mode 100644 lldb/test/Shell/RPC/Generator/Tests/Client/CheckSBPointer.test
create mode 100644 lldb/test/Shell/RPC/Generator/Tests/Client/CheckVoidPtr.test
create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.cpp
create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.h
create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.cpp
create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.h
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..65feabbf9c89b
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckArrayPointer.h
@@ -0,0 +1,17 @@
+#ifndef LLDB_API_SBRPC_CHECKARRAYPTR_H
+#define LLDB_API_SBRPC_CHECKARRAYPTR_H
+
+#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..ab0d6537011fa
--- /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 <cstddef>
+#include <cstdio>
+
+#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 <cstddef>
+#include <cstdio>
+
+#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..3335157418a3f
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckNonConstSBRef.h
@@ -0,0 +1,19 @@
+#ifndef LLDB_API_SBRPC_CHECKNONCONSTSBREF_H
+#define LLDB_API_SBRPC_CHECKNONCONSTSBREF_H
+
+#include <cstddef>
+
+#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..ce7915598c71c
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckSBPointer.h
@@ -0,0 +1,18 @@
+#ifndef LLDB_API_SBRPC_CHECKSBPTR_H
+#define LLDB_API_SBRPC_CHECKSBPTR_H
+
+#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..c1cd3d0f92532
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/Client/CheckVoidPtr.h
@@ -0,0 +1,18 @@
+#ifndef LLDB_API_SBRPC_CHECKVOIDPTR_H
+#define LLDB_API_SBRPC_CHECKVOIDPTR_H
+
+#include <cstddef>
+
+#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..6e5ef28c6c024
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckArrayPointer.test
@@ -0,0 +1,12 @@
+# Disabling until the lldb-rpc-gen tool lands.
+XFAIL: *
+RUN: mkdir -p %t/server
+RUN: mkdir -p %t/lib
+RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/Client/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: 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..9fff2a84a6990
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstCharPtrPtrWithLen.test
@@ -0,0 +1,11 @@
+# Disabling until the lldb-rpc-gen tool lands.
+XFAIL: *
+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: 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..0c2935e767a32
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstSBRef.test
@@ -0,0 +1,15 @@
+# Disabling until the lldb-rpc-gen tool lands.
+XFAIL: *
+RUN: mkdir -p %t/server
+RUN: mkdir -p %t/lib
+RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/Client/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: RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, *this);
+CHECK-NEXT: 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..547277ea6fc0c
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckNonConstSBRef.test
@@ -0,0 +1,14 @@
+# Disabling until the lldb-rpc-gen tool lands.
+XFAIL: *
+RUN: mkdir -p %t/server
+RUN: mkdir -p %t/lib
+RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/Client/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: if (connection_sp && !debugger_ref.ObjectRefIsValid())
+CHECK-NEXT: debugger_ref = lldb_rpc::SBDebugger(connection_sp);
+CHECK-NEXT: 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..bc86bab93243e
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckSBPointer.test
@@ -0,0 +1,17 @@
+# Disabling until the lldb-rpc-gen tool lands.
+XFAIL: *
+RUN: mkdir -p %t/server
+RUN: mkdir -p %t/lib
+RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/Client/CheckSBPointer.h
+
+RUN: cat %t/lib/CheckSBPointer.cpp
+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: if (debugger_ptr)
+CHECK-NEXT: RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, *debugger_ptr);
+CHECK-NEXT: else
+CHECK-NEXT: 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..6598fc50cfc34
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckVoidPtr.test
@@ -0,0 +1,11 @@
+# Disabling until the lldb-rpc-gen tool lands.
+XFAIL: *
+RUN: mkdir -p %t/server
+RUN: mkdir -p %t/lib
+RUN: %lldb-rpc-gen --output-dir=%t %S/../../Inputs/Client/CheckVoidPtr.h
+
+RUN: cat %t/lib/CheckVoidPtr.cpp | FileCheck %s
+
+# void * followed by length must use a Bytes object
+# when being encoded.
+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..a8c8645d1000d
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibraryHeaderEmitter.cpp
@@ -0,0 +1,126 @@
+#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.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);
+
+ if (method.IsCopyCtor)
+ CopyCtorEmitted = true;
+ else if (method.IsCopyAssign)
+ CopyAssignEmitted = true;
+ else if (method.IsMoveCtor)
+ MoveCtorEmitted = true;
+ else if (method.IsMoveAssign)
+ MoveAssignEmitted = true;
+}
+
+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 += " = " + std::string(ValueStr) + ", ";
+ 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 <lldb-rpc/common/RPCPublic.h>");
+ 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<llvm::ToolOutputFile> &&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..47f7f0b5d487f
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/client/RPCLibrarySourceEmitter.cpp
@@ -0,0 +1,544 @@
+#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 <string>
+
+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<RPCLibrarySourceEmitter::CustomLogic>
+ 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) {
+ EmitCommentHeader(method);
+ EmitFunctionHeader(method);
+ EmitFunctionBody(method);
+ EmitFunctionFooter();
+
+ 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;
+ }
+}
+
+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()) {
+ // 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;
+ } else {
+ // 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;
+ }
+ }
+ }
+
+ 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 + ", ";
+ StringListLine += std::next(Iter)->Name + ");";
+ EmitLine(StringListLine);
+ EmitLine(
+ "RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, " +
+ StringListName + ");");
+ } 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)) {
+ // 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.
+ //
+ // 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++;
+ const std::string ClassIdentifier =
+ "eClass_lldb_" + lldb_rpc_gen::GetSBClassNameFromType(Iter->Type);
+ const std::string ObjectRefType =
+ SBClassInheritsFromObjectRef(ClassIdentifier) ? "ObjectRef"
+ : "LocalObjectRef";
+ EmitLine(
+ "RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, "
+ "rpc::" +
+ ObjectRefType + "(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<llvm::ToolOutputFile> &&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 <lldb-rpc/common/RPCArgument.h>");
+ EmitLine("#include <lldb-rpc/common/RPCCommon.h>");
+ EmitLine("#include <lldb-rpc/common/RPCFunction.h>");
+ EmitLine("#include <lldb-rpc/common/RPCStringPool.h>");
+ EmitLine("#include <lldb-rpc/liblldbrpc/RPCUserClient.h>");
+ EmitLine("#include \"LLDBRPC.h\"");
+ EmitLine("#include <cassert>");
+ 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
index e6b601ea13012..747dc95561bef 100644
--- a/lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp
@@ -7,8 +7,10 @@
//===----------------------------------------------------------------------===//
#include "RPCCommon.h"
-#include "RPCServerHeaderEmitter.h"
-#include "RPCServerSourceEmitter.h"
+#include "client/RPCLibraryHeaderEmitter.h"
+#include "client/RPCLibrarySourceEmitter.h"
+#include "server/RPCServerHeaderEmitter.h"
+#include "server/RPCServerSourceEmitter.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
@@ -46,6 +48,12 @@ static std::string GetLibraryOutputDirectory() {
return std::string(Path);
}
+static std::string GetServerOutputDirectory() {
+ llvm::SmallString<128> Path(OutputDir.getValue());
+ llvm::sys::path::append(Path, "server");
+ return std::string(Path);
+}
+
static std::unique_ptr<llvm::ToolOutputFile>
CreateOutputFile(llvm::StringRef OutputDir, llvm::StringRef Filename) {
llvm::SmallString<128> Path(OutputDir);
@@ -79,10 +87,14 @@ class SBVisitor : public RecursiveASTVisitor<SBVisitor> {
SBVisitor(GeneratedByproducts &Byproducts, SourceManager &Manager,
ASTContext &Context,
std::unique_ptr<llvm::ToolOutputFile> &&ServerMethodOutputFile,
- std::unique_ptr<llvm::ToolOutputFile> &&ServerHeaderOutputFile)
+ std::unique_ptr<llvm::ToolOutputFile> &&ServerHeaderOutputFile,
+ std::unique_ptr<llvm::ToolOutputFile> &&LibrarySourceOutputFile,
+ std::unique_ptr<llvm::ToolOutputFile> &&LibraryHeaderOutputFile)
: Byproducts(Byproducts), Manager(Manager), Context(Context),
ServerSourceEmitter(std::move(ServerMethodOutputFile)),
- ServerHeaderEmitter(std::move(ServerHeaderOutputFile)) {}
+ ServerHeaderEmitter(std::move(ServerHeaderOutputFile)),
+ LibrarySourceEmitter(std::move(LibrarySourceOutputFile)),
+ LibraryHeaderEmitter(std::move(LibraryHeaderOutputFile)) {}
~SBVisitor() {}
@@ -97,10 +109,17 @@ class SBVisitor : public RecursiveASTVisitor<SBVisitor> {
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<EnumDecl>(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 IsDisallowed =
+ lldb_rpc_gen::MethodIsDisallowed(Context, MDecl);
const bool HasCallbackParameter =
lldb_rpc_gen::HasCallbackParameter(MDecl);
SupportLevel MethodSupportLevel = GetMethodSupportLevel(MDecl);
@@ -108,10 +127,14 @@ class SBVisitor : public RecursiveASTVisitor<SBVisitor> {
const lldb_rpc_gen::Method Method(MDecl, Policy, Context);
ServerSourceEmitter.EmitMethod(Method);
ServerHeaderEmitter.EmitMethod(Method);
+ LibrarySourceEmitter.EmitMethod(Method);
+ LibraryHeaderEmitter.EmitMethod(Method);
Byproducts.MangledMethodNames.insert(MangledName);
} else if (MethodSupportLevel == eUnimplemented)
Byproducts.SkippedMethodNames.insert(MangledName);
}
+ LibraryHeaderEmitter.EndClass();
+ LibrarySourceEmitter.EndClass();
return true;
}
@@ -211,6 +234,8 @@ class SBVisitor : public RecursiveASTVisitor<SBVisitor> {
ASTContext &Context;
lldb_rpc_gen::RPCServerSourceEmitter ServerSourceEmitter;
lldb_rpc_gen::RPCServerHeaderEmitter ServerHeaderEmitter;
+ lldb_rpc_gen::RPCLibrarySourceEmitter LibrarySourceEmitter;
+ lldb_rpc_gen::RPCLibraryHeaderEmitter LibraryHeaderEmitter;
};
class SBConsumer : public ASTConsumer {
@@ -218,9 +243,13 @@ class SBConsumer : public ASTConsumer {
SBConsumer(GeneratedByproducts &Byproducts, SourceManager &Manager,
ASTContext &Context,
std::unique_ptr<llvm::ToolOutputFile> &&ServerMethodOutputFile,
- std::unique_ptr<llvm::ToolOutputFile> &&ServerHeaderOutputFile)
+ std::unique_ptr<llvm::ToolOutputFile> &&ServerHeaderOutputFile,
+ std::unique_ptr<llvm::ToolOutputFile> &&LibrarySourceOutputFile,
+ std::unique_ptr<llvm::ToolOutputFile> &&LibraryHeaderOutputFile)
: Visitor(Byproducts, Manager, Context, std::move(ServerMethodOutputFile),
- std::move(ServerHeaderOutputFile)) {}
+ std::move(ServerHeaderOutputFile),
+ std::move(LibrarySourceOutputFile),
+ std::move(LibraryHeaderOutputFile)) {}
bool HandleTopLevelDecl(DeclGroupRef DR) override {
for (Decl *D : DR)
Visitor.TraverseDecl(D);
@@ -255,11 +284,26 @@ class SBAction : public ASTFrontendAction {
if (!ServerHeaderOutputFile)
return nullptr;
+ const std::string LibrarySourceFilename = FilenameNoExt.str() + ".cpp";
+ std::unique_ptr<llvm::ToolOutputFile> LibrarySourceOutputFile =
+ CreateOutputFile(GetLibraryOutputDirectory(), LibrarySourceFilename);
+ if (!LibrarySourceOutputFile)
+ return nullptr;
+
+ const std::string LibraryHeaderFilename = FilenameNoExt.str() + ".h";
+ std::unique_ptr<llvm::ToolOutputFile> LibraryHeaderOutputFile =
+ CreateOutputFile(GetLibraryOutputDirectory(), LibraryHeaderFilename);
+ if (!LibraryHeaderOutputFile)
+ return nullptr;
+
ServerMethodOutputFile->keep();
ServerHeaderOutputFile->keep();
+ LibrarySourceOutputFile->keep();
+ LibraryHeaderOutputFile->keep();
return std::make_unique<SBConsumer>(
Byproducts, CI.getSourceManager(), CI.getASTContext(),
- std::move(ServerMethodOutputFile), std::move(ServerHeaderOutputFile));
+ std::move(ServerMethodOutputFile), std::move(ServerHeaderOutputFile),
+ std::move(LibrarySourceOutputFile), std::move(LibraryHeaderOutputFile));
}
private:
@@ -307,6 +351,28 @@ bool EmitAmalgamatedServerHeader(const std::vector<std::string> &Files) {
return true;
}
+bool EmitAmalgamatedLibraryHeader(const std::vector<std::string> &Files) {
+ static constexpr llvm::StringLiteral AmalgamatedLibraryHeaderName =
+ "LLDBRPC.h";
+ std::unique_ptr<llvm::ToolOutputFile> 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<std::string> &ClassNames) {
static constexpr llvm::StringLiteral ClassNamesFileName = "SBClasses.def";
std::unique_ptr<llvm::ToolOutputFile> ClassNamesFile =
@@ -381,6 +447,19 @@ int main(int argc, const char *argv[]) {
return 1;
}
+ // Create the output directory if the user specified one does not exist.
+ if (!llvm::sys::fs::exists(OutputDir.getValue())) {
+ llvm::sys::fs::create_directory(OutputDir.getValue());
+ }
+
+ if (!llvm::sys::fs::exists(GetServerOutputDirectory())) {
+ llvm::sys::fs::create_directory(GetServerOutputDirectory());
+ }
+
+ if (!llvm::sys::fs::exists(GetLibraryOutputDirectory())) {
+ llvm::sys::fs::create_directory(GetLibraryOutputDirectory());
+ }
+
CommonOptionsParser &OP = ExpectedParser.get();
auto PCHOpts = std::make_shared<PCHContainerOperations>();
PCHOpts->registerWriter(std::make_unique<ObjectFilePCHContainerWriter>());
@@ -390,6 +469,10 @@ int main(int argc, const char *argv[]) {
if (!EmitAmalgamatedServerHeader(OP.getSourcePathList())) {
llvm::errs() << "Failed to create amalgamated server header\n";
+ }
+
+ if (!EmitAmalgamatedLibraryHeader(OP.getSourcePathList())) {
+ llvm::errs() << "Failed to create amalgamated library header\n";
return 1;
}
More information about the lldb-commits
mailing list