[Lldb-commits] [lldb] [lldb] Upstream lldb-rpc-gen and LLDB RPC server-side emitters (PR #136748)
Chelsea Cassanova via lldb-commits
lldb-commits at lists.llvm.org
Tue Apr 29 15:35:09 PDT 2025
https://github.com/chelcassanova updated https://github.com/llvm/llvm-project/pull/136748
>From 9ab6fdfcb8098648978705b4e86b3c1cc8b14662 Mon Sep 17 00:00:00 2001
From: Chelsea Cassanova <chelsea_cassanova at apple.com>
Date: Thu, 17 Apr 2025 15:36:16 -0700
Subject: [PATCH] [DRAFT][lldb] Upstream lldb-rpc-gen and LLDB RPC server-side
emitters
This commit upstreams lldb-rpc-gen, a tool that emits the client and
server interfaces used for LLDB RPC. This is the initial commit in the
upstreaming process for LLDB RPC. lldb-rpc-gen is a ClangTool that reads
the LLDB SB API headers and uses their information to generate the
interfaces for RPC. This commit specifically adds the server-side
emitters for easier review. The client-side interface will be added in a
later commit.
RFC: https://discourse.llvm.org/t/rfc-upstreaming-lldb-rpc/85804
---
.../convert-lldb-header-to-rpc-header.py | 65 ++
lldb/scripts/framework-header-include-fix.py | 44 ++
lldb/scripts/framework-header-version-fix.py | 65 ++
.../RPC/Generator/Inputs/CheckArrayPointer.h | 20 +
.../Inputs/CheckConstCharPtrPtrWithLen.h | 19 +
.../RPC/Generator/Inputs/CheckConstSBRef.h | 19 +
.../RPC/Generator/Inputs/CheckNonConstSBRef.h | 20 +
.../RPC/Generator/Inputs/CheckSBPointer.h | 21 +
.../Shell/RPC/Generator/Inputs/CheckVoidPtr.h | 19 +
.../Tests/Client/CheckArrayPointer.test | 13 +
.../Client/CheckConstCharPtrPtrWithLen.test | 12 +
.../Tests/Client/CheckConstSBRef.test | 16 +
.../Tests/Client/CheckNonConstSBRef.test | 15 +
.../Tests/Client/CheckSBPointer.test | 17 +
.../Generator/Tests/Client/CheckVoidPtr.test | 12 +
lldb/tools/CMakeLists.txt | 2 +
lldb/tools/lldb-rpc/CMakeLists.txt | 16 +
lldb/tools/lldb-rpc/LLDBRPCGeneration.cmake | 95 +++
lldb/tools/lldb-rpc/LLDBRPCHeaders.cmake | 114 ++++
.../lldb-rpc/lldb-rpc-gen/CMakeLists.txt | 23 +
.../tools/lldb-rpc/lldb-rpc-gen/RPCCommon.cpp | 535 ++++++++++++++++
lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.h | 105 ++++
.../lldb-rpc-gen/RPCServerHeaderEmitter.cpp | 73 +++
.../lldb-rpc-gen/RPCServerHeaderEmitter.h | 46 ++
.../lldb-rpc-gen/RPCServerSourceEmitter.cpp | 592 ++++++++++++++++++
.../lldb-rpc-gen/RPCServerSourceEmitter.h | 80 +++
.../lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp | 540 ++++++++++++++++
27 files changed, 2598 insertions(+)
create mode 100755 lldb/scripts/convert-lldb-header-to-rpc-header.py
create mode 100755 lldb/scripts/framework-header-include-fix.py
create mode 100755 lldb/scripts/framework-header-version-fix.py
create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/CheckArrayPointer.h
create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/CheckConstCharPtrPtrWithLen.h
create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/CheckConstSBRef.h
create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/CheckNonConstSBRef.h
create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/CheckSBPointer.h
create mode 100644 lldb/test/Shell/RPC/Generator/Inputs/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/CMakeLists.txt
create mode 100644 lldb/tools/lldb-rpc/LLDBRPCGeneration.cmake
create mode 100644 lldb/tools/lldb-rpc/LLDBRPCHeaders.cmake
create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/CMakeLists.txt
create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.cpp
create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.h
create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerHeaderEmitter.cpp
create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerHeaderEmitter.h
create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerSourceEmitter.cpp
create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerSourceEmitter.h
create mode 100644 lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp
diff --git a/lldb/scripts/convert-lldb-header-to-rpc-header.py b/lldb/scripts/convert-lldb-header-to-rpc-header.py
new file mode 100755
index 0000000000000..4550837d8e08d
--- /dev/null
+++ b/lldb/scripts/convert-lldb-header-to-rpc-header.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+# Usage: convert-lldb-header-to-rpc-header.py <path/to/input-header.h> <path/to/output-header.h>
+# This scripts takes common LLDB headers (such as lldb-defines.h) and replaces references to LLDB
+# with those for RPC. This happens for:
+# - namespace definitions
+# - namespace usage
+# - version string macros
+# - ifdef/ifndef lines
+
+import argparse
+import os
+import re
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("input")
+ parser.add_argument("output")
+ args = parser.parse_args()
+ input_path = str(args.input)
+ output_path = str(args.output)
+ with open(input_path, "r") as input_file:
+ lines = input_file.readlines()
+
+ with open(output_path, "w") as output_file:
+ for line in lines:
+ # NOTE: We do not use lldb-forward.h or lldb-versioning.h in RPC, so remove
+ # all includes that are found for these files.
+ if re.match(
+ r'#include "lldb/lldb-forward|#include "lldb/lldb-versioning', line
+ ):
+ continue
+ # For lldb-rpc-defines.h, replace the ifndef LLDB_LLDB_ portion with LLDB_RPC_ as we're not
+ # using LLDB private definitions in RPC.
+ elif re.match(r".+LLDB_LLDB_", line):
+ output_file.write(re.sub(r"LLDB_LLDB_", r"LLDB_RPC_", line))
+ # Similarly to lldb-rpc-defines.h, replace the ifndef for LLDB_API in SBDefines.h to LLDB_RPC_API_ for the same reason.
+ elif re.match(r".+LLDB_API_", line):
+ output_file.write(re.sub(r"LLDB_API_", r"LLDB_RPC_API_", line))
+ # Replace the references for the macros that define the versioning strings in
+ # lldb-rpc-defines.h.
+ elif re.match(r".+LLDB_VERSION", line):
+ output_file.write(re.sub(r"LLDB_VERSION", r"LLDB_RPC_VERSION", line))
+ elif re.match(r".+LLDB_REVISION", line):
+ output_file.write(re.sub(r"LLDB_REVISION", r"LLDB_RPC_REVISION", line))
+ elif re.match(r".+LLDB_VERSION_STRING", line):
+ output_file.write(
+ re.sub(r"LLDB_VERSION_STRING", r"LLDB_RPC_VERSION_STRING", line)
+ )
+ # For local #includes
+ elif re.match(r'#include "lldb/lldb-', line):
+ output_file.write(re.sub(r"lldb/lldb-", r"lldb-rpc-", line))
+ # Rename the lldb namespace definition to lldb-rpc.
+ elif re.match(r"namespace lldb", line):
+ output_file.write(re.sub(r"lldb", r"lldb_rpc", line))
+ # Rename namespace references
+ elif re.match(r".+lldb::", line):
+ output_file.write(re.sub(r"lldb::", r"lldb_rpc::", line))
+ else:
+ # Write any line that doesn't need to be converted
+ output_file.write(line)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/lldb/scripts/framework-header-include-fix.py b/lldb/scripts/framework-header-include-fix.py
new file mode 100755
index 0000000000000..e214ce005923f
--- /dev/null
+++ b/lldb/scripts/framework-header-include-fix.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+# Usage: framework-header-include-fix.py <path/to/input-header.h> <path/to/output-header.h>
+# This script modifies all #include lines in all lldb-rpc headers
+# from either filesystem or local includes to liblldbrpc includes.
+
+import argparse
+import os
+import re
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("input")
+ parser.add_argument("output")
+ args = parser.parse_args()
+ input_path = str(args.input)
+ output_path = str(args.output)
+ with open(input_path, "r+") as input_file:
+ lines = input_file.readlines()
+
+ with open(output_path, "w+") as output_file:
+ for line in lines:
+ # Replace includes from RPCCommon to liblldbrpc includes.
+ # e.g. #include <lldb-rpc/common/RPCArgument.h> -> #include <LLDBRPC/RPCArgument.h>
+ if re.match(r".+<lldb-rpc/common", line):
+ output_file.write(re.sub(r"<lldb-rpc/common", r"<LLDBRPC", line))
+ # Replace all local file includes to liblldbrpc includes.
+ # e.g. #include "SBFoo.h" -> #include <LLDBRPC/SBFoo.h>
+ elif re.match(r'#include "(.*)"', line):
+ include_filename = re.search(r'#include "(.*)"', line).groups()[0]
+ output_file.write(
+ re.sub(
+ r'#include "(.*)"',
+ r"#include <LLDBRPC/" + include_filename + ">",
+ line,
+ )
+ )
+ else:
+ # Write any line that doesn't need to be converted
+ output_file.write(line)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/lldb/scripts/framework-header-version-fix.py b/lldb/scripts/framework-header-version-fix.py
new file mode 100755
index 0000000000000..72185f8e820ce
--- /dev/null
+++ b/lldb/scripts/framework-header-version-fix.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+# Usage: framework-header-version-fix.py <path/to/input-header.h> <path/to/output-header.h> MAJOR MINOR PATCH
+# This script modifies lldb-rpc-defines.h to uncomment the macro defines used for the LLDB
+# major, minor and patch values as well as populating their definitions.
+
+import argparse
+import os
+import re
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("input")
+ parser.add_argument("output")
+ parser.add_argument("lldb_version_major")
+ parser.add_argument("lldb_version_minor")
+ parser.add_argument("lldb_version_patch")
+ args = parser.parse_args()
+ input_path = str(args.input)
+ output_path = str(args.output)
+ lldb_version_major = args.lldb_version_major
+ lldb_version_minor = args.lldb_version_minor
+ lldb_version_patch = args.lldb_version_patch
+
+ with open(input_path, "r") as input_file:
+ lines = input_file.readlines()
+
+ with open(output_path, "w") as output_file:
+ for line in lines:
+ # Uncomment the line that defines the LLDB major version and populate its value.
+ if re.match(r"//#define LLDB_RPC_VERSION$", line):
+ output_file.write(
+ re.sub(
+ r"//#define LLDB_RPC_VERSION",
+ r"#define LLDB_RPC_VERSION " + lldb_version_major,
+ line,
+ )
+ )
+ # Uncomment the line that defines the LLDB minor version and populate its value.
+ elif re.match(r"//#define LLDB_RPC_REVISION$", line):
+ output_file.write(
+ re.sub(
+ r"//#define LLDB_RPC_REVISION",
+ r"#define LLDB_RPC_REVISION " + lldb_version_minor,
+ line,
+ )
+ )
+ # Uncomment the line that defines the complete LLDB version string and populate its value.
+ elif re.match(r"//#define LLDB_RPC_VERSION_STRING$", line):
+ output_file.write(
+ re.sub(
+ r"//#define LLDB_RPC_VERSION_STRING",
+ r'#define LLDB_RPC_VERSION_STRING "{0}.{1}.{2}"'.format(
+ lldb_version_major, lldb_version_minor, lldb_version_patch
+ ),
+ line,
+ )
+ )
+ else:
+ # Write any line that doesn't need to be converted
+ output_file.write(line)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/lldb/test/Shell/RPC/Generator/Inputs/CheckArrayPointer.h b/lldb/test/Shell/RPC/Generator/Inputs/CheckArrayPointer.h
new file mode 100644
index 0000000000000..18ec92a976b8a
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/CheckArrayPointer.h
@@ -0,0 +1,20 @@
+#ifndef LLDB_API_SBRPC_CHECKARRAYPTR_H
+#define LLDB_API_SBRPC_CHECKARRAYPTR_H
+
+#include <cstddef>
+#include <cstdio>
+
+#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/CheckConstCharPtrPtrWithLen.h b/lldb/test/Shell/RPC/Generator/Inputs/CheckConstCharPtrPtrWithLen.h
new file mode 100644
index 0000000000000..fa64492cf5aa7
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/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/CheckConstSBRef.h b/lldb/test/Shell/RPC/Generator/Inputs/CheckConstSBRef.h
new file mode 100644
index 0000000000000..153fdad60a494
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/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/CheckNonConstSBRef.h b/lldb/test/Shell/RPC/Generator/Inputs/CheckNonConstSBRef.h
new file mode 100644
index 0000000000000..90ee3d3a7fbd6
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/CheckNonConstSBRef.h
@@ -0,0 +1,20 @@
+#ifndef LLDB_API_SBRPC_CHECKNONCONSTSBREF_H
+#define LLDB_API_SBRPC_CHECKNONCONSTSBREF_H
+
+#include <cstddef>
+#include <cstdio>
+
+#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/CheckSBPointer.h b/lldb/test/Shell/RPC/Generator/Inputs/CheckSBPointer.h
new file mode 100644
index 0000000000000..d9b7efa820f53
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/CheckSBPointer.h
@@ -0,0 +1,21 @@
+#ifndef LLDB_API_SBRPC_CHECKSBPTR_H
+#define LLDB_API_SBRPC_CHECKSBPTR_H
+
+#include <cstddef>
+#include <cstdio>
+
+#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/CheckVoidPtr.h b/lldb/test/Shell/RPC/Generator/Inputs/CheckVoidPtr.h
new file mode 100644
index 0000000000000..fa6484fc8d7cd
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Inputs/CheckVoidPtr.h
@@ -0,0 +1,19 @@
+#ifndef LLDB_API_SBRPC_CHECKVOIDPTR_H
+#define LLDB_API_SBRPC_CHECKVOIDPTR_H
+
+#include <cstddef>
+#include <cstdio>
+
+#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..0b19b9384f811
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckArrayPointer.test
@@ -0,0 +1,13 @@
+// Skipping temporarily due to rdar://149500008
+# XFAIL: system-darwin
+# 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..1bde252dbc033
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstCharPtrPtrWithLen.test
@@ -0,0 +1,12 @@
+// Skipping temporarily due to rdar://149500008
+# XFAIL: system-darwin
+# 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..879e2227d8721
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckConstSBRef.test
@@ -0,0 +1,16 @@
+// Skipping temporarily due to rdar://149500008
+# XFAIL: system-darwin
+# 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..3ab54867e6fd5
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckNonConstSBRef.test
@@ -0,0 +1,15 @@
+// Skipping temporarily due to rdar://149500008
+# XFAIL: system-darwin
+# 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..e86e492ec37f6
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckSBPointer.test
@@ -0,0 +1,17 @@
+// Skipping temporarily due to rdar://149500008
+# XFAIL: system-darwin
+# 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..31fdec68f7d3f
--- /dev/null
+++ b/lldb/test/Shell/RPC/Generator/Tests/Client/CheckVoidPtr.test
@@ -0,0 +1,12 @@
+// Skipping temporarily due to rdar://149500008
+# XFAIL: system-darwin
+# 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/CMakeLists.txt b/lldb/tools/CMakeLists.txt
index 6804dc234555b..6a2859d042148 100644
--- a/lldb/tools/CMakeLists.txt
+++ b/lldb/tools/CMakeLists.txt
@@ -7,6 +7,8 @@ add_subdirectory(intel-features)
# example is `check-lldb`. So, we pass EXCLUDE_FROM_ALL here.
add_subdirectory(lldb-test EXCLUDE_FROM_ALL)
add_subdirectory(lldb-fuzzer EXCLUDE_FROM_ALL)
+add_subdirectory(lldb-rpc EXCLUDE_FROM_ALL)
+
add_lldb_tool_subdirectory(lldb-instr)
add_lldb_tool_subdirectory(lldb-dap)
diff --git a/lldb/tools/lldb-rpc/CMakeLists.txt b/lldb/tools/lldb-rpc/CMakeLists.txt
new file mode 100644
index 0000000000000..d6dff072c9e3a
--- /dev/null
+++ b/lldb/tools/lldb-rpc/CMakeLists.txt
@@ -0,0 +1,16 @@
+if(LLDB_CODESIGN_IDENTITY)
+ # Use explicit LLDB identity
+ set(LLVM_CODESIGNING_IDENTITY ${LLDB_CODESIGN_IDENTITY})
+else()
+ # Use explicit LLVM identity or default to ad-hoc signing if empty
+ if(NOT LLVM_CODESIGNING_IDENTITY)
+ set(LLVM_CODESIGNING_IDENTITY -)
+ endif()
+endif()
+
+# LLDBRPCGeneration.cmake needs the LLDB_RPC_GEN_EXE variable
+# which gets defined in the lldb-rpc-gen folder, so we're adding
+# this folder before we add that file.
+add_lldb_tool_subdirectory(lldb-rpc-gen)
+include(${CMAKE_CURRENT_SOURCE_DIR}/LLDBRPCGeneration.cmake)
+include(${CMAKE_CURRENT_SOURCE_DIR}/LLDBRPCHeaders.cmake)
diff --git a/lldb/tools/lldb-rpc/LLDBRPCGeneration.cmake b/lldb/tools/lldb-rpc/LLDBRPCGeneration.cmake
new file mode 100644
index 0000000000000..c0a041f3e96b7
--- /dev/null
+++ b/lldb/tools/lldb-rpc/LLDBRPCGeneration.cmake
@@ -0,0 +1,95 @@
+if (NOT DEFINED LLDB_RPC_GEN_EXE)
+ message(FATAL_ERROR
+ "Unable to generate lldb-rpc sources because LLDB_RPC_GEN_EXE is not
+ defined. If you are cross-compiling, please build lldb-rpc-gen for your host
+ platform.")
+endif()
+set(lldb_rpc_generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated")
+set(lldb_rpc_server_generated_source_dir "${lldb_rpc_generated_dir}/server")
+set(lldb_rpc_lib_generated_source_dir "${lldb_rpc_generated_dir}/lib")
+set(lldb_rpc_client_generated_source_dir "${lldb_rpc_generated_dir}/client")
+
+file(GLOB api_headers ${LLDB_SOURCE_DIR}/include/lldb/API/SB*.h)
+# We don't generate SBCommunication
+list(REMOVE_ITEM api_headers ${LLDB_SOURCE_DIR}/include/lldb/API/SBCommunication.h)
+# SBDefines.h is mostly definitions and forward declarations, nothing to
+# generate.
+list(REMOVE_ITEM api_headers ${LLDB_SOURCE_DIR}/include/lldb/API/SBDefines.h)
+
+# Generate the list of byproducts. Note that we cannot just glob the files in
+# the directory with the generated sources because BYPRODUCTS needs to be known
+# at configure time but the files are generated at build time.
+set(lldb_rpc_gen_byproducts
+ ${lldb_rpc_generated_dir}/SBClasses.def
+ ${lldb_rpc_generated_dir}/SBAPI.def
+ ${lldb_rpc_generated_dir}/lldb.py
+ ${lldb_rpc_server_generated_source_dir}/SBAPI.h
+ ${lldb_rpc_lib_generated_source_dir}/LLDBRPC.h
+)
+
+set(lldb_rpc_gen_server_impl_files)
+set(lldb_rpc_gen_lib_header_files)
+set(lldb_rpc_gen_lib_impl_files)
+foreach(path ${api_headers})
+ get_filename_component(filename_no_ext ${path} NAME_WLE)
+
+ set(server_header_file "Server_${filename_no_ext}.h")
+ list(APPEND lldb_rpc_gen_byproducts "${lldb_rpc_server_generated_source_dir}/${server_header_file}")
+
+ set(server_impl_file "Server_${filename_no_ext}.cpp")
+ list(APPEND lldb_rpc_gen_byproducts "${lldb_rpc_server_generated_source_dir}/${server_impl_file}")
+ list(APPEND lldb_rpc_gen_server_impl_files "${lldb_rpc_server_generated_source_dir}/${server_impl_file}")
+
+ set(lib_header_file "${filename_no_ext}.h")
+ list(APPEND lldb_rpc_gen_byproducts "${lldb_rpc_lib_generated_source_dir}/${lib_header_file}")
+ list(APPEND lldb_rpc_gen_lib_header_files "${lldb_rpc_lib_generated_source_dir}/${lib_header_file}")
+
+ set(lib_impl_file "${filename_no_ext}.cpp")
+ list(APPEND lldb_rpc_gen_byproducts "${lldb_rpc_lib_generated_source_dir}/${lib_impl_file}")
+ list(APPEND lldb_rpc_gen_lib_impl_files "${lldb_rpc_lib_generated_source_dir}/${lib_impl_file}")
+endforeach()
+list(APPEND lldb_rpc_gen_lib_impl_files "${CMAKE_CURRENT_BINARY_DIR}/generated/lib/RPCClientSideCallbacks.cpp")
+list(APPEND lldb_rpc_gen_lib_header_files "${lldb_rpc_lib_generated_source_dir}/LLDBRPC.h")
+
+# Make sure that the clang-resource-dir is set correctly or else the tool will
+# fail to run. This is only needed when we do a standalone build.
+set(clang_resource_dir_arg)
+if (LLDB_BUILT_STANDALONE)
+ if (TARGET clang-resource-headers)
+ set(clang_resource_headers_dir
+ $<TARGET_PROPERTY:clang-resource-headers,INTERFACE_INCLUDE_DIRECTORIES>)
+ set(clang_resource_dir_arg --extra-arg="-resource-dir=${clang_resource_headers_dir}/..")
+ else()
+ set(clang_resource_dir_arg --extra-arg="-resource-dir=${LLDB_EXTERNAL_CLANG_RESOURCE_DIR}")
+ endif()
+endif()
+
+add_custom_command(OUTPUT ${lldb_rpc_gen_byproducts}
+ COMMAND ${CMAKE_COMMAND} -E make_directory
+ ${lldb_rpc_generated_dir}
+
+ COMMAND ${CMAKE_COMMAND} -E make_directory
+ ${lldb_rpc_server_generated_source_dir}
+
+ COMMAND ${CMAKE_COMMAND} -E make_directory
+ ${lldb_rpc_lib_generated_source_dir}
+
+ COMMAND ${CMAKE_COMMAND} -E make_directory
+ ${lldb_rpc_client_generated_source_dir}
+
+ COMMAND ${LLDB_RPC_GEN_EXE}
+ -p ${CMAKE_BINARY_DIR}
+ --output-dir=${lldb_rpc_generated_dir}
+ ${clang_resource_dir_arg}
+ --extra-arg="-USWIG"
+ ${api_headers}
+
+ DEPENDS ${LLDB_RPC_GEN_EXE} ${api_headers}
+ COMMENT "Generating sources for lldb-rpc-server..."
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+)
+
+add_custom_target(lldb-rpc-generate-sources
+ DEPENDS
+ ${lldb_rpc_gen_byproducts}
+ lldb-sbapi-dwarf-enums)
diff --git a/lldb/tools/lldb-rpc/LLDBRPCHeaders.cmake b/lldb/tools/lldb-rpc/LLDBRPCHeaders.cmake
new file mode 100644
index 0000000000000..e8b831c56103f
--- /dev/null
+++ b/lldb/tools/lldb-rpc/LLDBRPCHeaders.cmake
@@ -0,0 +1,114 @@
+set(derived_headers_location "${CMAKE_CURRENT_BINARY_DIR}/DerivedHeaders")
+set(original_headers_location "${LLDB_SOURCE_DIR}/include/lldb")
+set(headers_to_process
+ API/SBDefines.h
+ lldb-defines.h
+ lldb-enumerations.h
+ lldb-types.h
+)
+
+file(MAKE_DIRECTORY ${derived_headers_location})
+
+set(original_headers)
+set(derived_headers)
+foreach(header ${headers_to_process})
+ set(original_header "${original_headers_location}/${header}")
+
+ get_filename_component(header_filename ${header} NAME)
+ string(REPLACE "lldb-" "lldb-rpc-" rpc_header_filename "${header_filename}")
+ set(derived_header "${derived_headers_location}/${rpc_header_filename}")
+
+ list(APPEND original_headers "${original_header}")
+ list(APPEND derived_headers "${derived_header}")
+ add_custom_command(OUTPUT ${derived_header}
+ COMMAND ${LLDB_SOURCE_DIR}/scripts/convert-lldb-header-to-rpc-header.py
+ ${original_header} ${derived_header}
+ DEPENDS ${original_header}
+
+ COMMENT "Creating ${derived_header}"
+ )
+endforeach()
+
+set(generated_headers_to_process
+ API/SBLanguages.h
+)
+foreach(header ${generated_headers_to_process})
+ set(original_header "${LLDB_OBJ_DIR}/include/lldb/${header}")
+
+ get_filename_component(header_filename ${header} NAME)
+ string(REPLACE "lldb-" "lldb-rpc-" rpc_header_filename "${header_filename}")
+ set(derived_header "${derived_headers_location}/${rpc_header_filename}")
+
+ list(APPEND original_headers "${original_header}")
+ list(APPEND derived_headers "${derived_header}")
+ add_custom_command(OUTPUT ${derived_header}
+ COMMAND ${LLDB_SOURCE_DIR}/scripts/convert-lldb-header-to-rpc-header.py
+ ${original_header} ${derived_header}
+ DEPENDS lldb-sbapi-dwarf-enums
+
+ COMMENT "Creating ${derived_header}"
+ )
+endforeach()
+
+
+add_custom_target(copy-aux-rpc-headers DEPENDS ${derived_headers})
+
+set(public_headers ${lldb_rpc_gen_lib_header_files})
+list(APPEND public_headers
+ ${derived_headers_location}/SBDefines.h
+ ${derived_headers_location}/SBLanguages.h
+ ${derived_headers_location}/lldb-rpc-enumerations.h
+ ${derived_headers_location}/lldb-rpc-types.h
+)
+
+# Collect and preprocess headers for the framework bundle
+set(version_header
+ ${derived_headers_location}/lldb-rpc-defines.h
+)
+
+function(FixIncludePaths in subfolder out)
+ get_filename_component(base_name ${in} NAME)
+ set(parked_header ${CMAKE_CURRENT_BINARY_DIR}/ParkedHeaders/${subfolder}/${base_name})
+ set(${out} ${parked_header} PARENT_SCOPE)
+
+ add_custom_command(OUTPUT ${parked_header}
+ COMMAND ${LLDB_SOURCE_DIR}/scripts/framework-header-include-fix.py
+ ${in} ${parked_header}
+ DEPENDS ${in}
+ COMMENT "Fixing includes in ${in}"
+ )
+endfunction()
+
+function(FixVersions in subfolder out)
+ get_filename_component(base_name ${in} NAME)
+ set(parked_header ${CMAKE_CURRENT_BINARY_DIR}/ParkedHeaders/${subfolder}/${base_name})
+ set(${out} ${parked_header} PARENT_SCOPE)
+
+ add_custom_command(OUTPUT ${parked_header}
+ COMMAND ${LLDB_SOURCE_DIR}/scripts/framework-header-version-fix.py
+ ${in} ${parked_header} ${LLDB_VERSION_MAJOR} ${LLDB_VERSION_MINOR} ${LLDB_VERSION_PATCH}
+ DEPENDS ${in}
+ COMMENT "Fixing versions in ${liblldbrpc_version_header}"
+ )
+endfunction()
+
+set(preprocessed_headers)
+
+# Apply include-paths fix on all headers and park them.
+foreach(source_header ${public_headers})
+ FixIncludePaths(${source_header} Headers parked_header)
+ list(APPEND preprocessed_headers ${parked_header})
+endforeach()
+
+# Apply include-paths fix and stage in parent directory.
+# Then apply version fix and park together with all the others.
+FixIncludePaths(${version_header} ".." staged_header)
+FixVersions(${staged_header} Headers parked_header)
+list(APPEND preprocessed_headers ${parked_header})
+
+# Wrap header preprocessing in a target, so liblldbrpc can depend on.
+add_custom_target(liblldbrpc-headers DEPENDS ${preprocessed_headers})
+add_dependencies(liblldbrpc-headers copy-aux-rpc-headers)
+set_target_properties(liblldbrpc-headers PROPERTIES
+ LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ParkedHeaders
+)
diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/CMakeLists.txt b/lldb/tools/lldb-rpc/lldb-rpc-gen/CMakeLists.txt
new file mode 100644
index 0000000000000..bc1be9865e159
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/CMakeLists.txt
@@ -0,0 +1,23 @@
+add_lldb_tool(lldb-rpc-gen
+ RPCCommon.cpp
+ RPCServerHeaderEmitter.cpp
+ RPCServerSourceEmitter.cpp
+ lldb-rpc-gen.cpp
+
+ CLANG_LIBS
+ clangAST
+ clangBasic
+ clangCodeGen
+ clangFrontend
+ clangLex
+ clangRewrite
+ clangSerialization
+ clangTooling
+
+ LINK_COMPONENTS
+ Support
+ )
+
+if (NOT DEFINED LLDB_RPC_GEN_EXE)
+ set(LLDB_RPC_GEN_EXE $<TARGET_FILE:lldb-rpc-gen> CACHE STRING "Executable that generates lldb-rpc-server")
+endif()
diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.cpp b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.cpp
new file mode 100644
index 0000000000000..2059ada774ccb
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.cpp
@@ -0,0 +1,535 @@
+//===-- RPCCommon.cpp -----------------------------------------------------===//
+//
+// 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 "clang/AST/AST.h"
+#include "clang/AST/Mangle.h"
+#include "clang/Lex/Lexer.h"
+
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace clang;
+
+// We intentionally do not generate some classes because they are currently
+// inconvenient, they aren't really used by most consumers, or we're not sure
+// why they exist.
+static constexpr llvm::StringRef DisallowedClasses[] = {
+ "SBCommunication", // What is this used for?
+ "SBInputReader", // What is this used for?
+ "SBCommandPluginInterface", // This is hard to support, we can do it if
+ // really needed though.
+ "SBCommand", // There's nothing too difficult about this one, but many of
+ // its methods take a SBCommandPluginInterface pointer so
+ // there's no reason to support this.
+};
+
+// We intentionally avoid generating certain methods either because they are
+// difficult to support correctly or they aren't really used much from C++.
+// FIXME: We should be able to annotate these methods instead of maintaining a
+// list in the generator itself.
+static constexpr llvm::StringRef DisallowedMethods[] = {
+ // The threading functionality in SBHostOS is deprecated and thus we do not
+ // generate them. It would be ideal to add the annotations to the methods
+ // and then support not generating deprecated methods. However, without
+ // annotations the generator generates most things correctly. This one is
+ // problematic because it returns a pointer to an "opaque" structure
+ // (thread_t) that is not `void *`, so special casing it is more effort than
+ // it's worth.
+ "_ZN4lldb8SBHostOS10ThreadJoinEP17_opaque_pthread_tPPvPNS_7SBErrorE",
+ "_ZN4lldb8SBHostOS12ThreadCancelEP17_opaque_pthread_tPNS_7SBErrorE",
+ "_ZN4lldb8SBHostOS12ThreadCreateEPKcPFPvS3_ES3_PNS_7SBErrorE",
+ "_ZN4lldb8SBHostOS12ThreadDetachEP17_opaque_pthread_tPNS_7SBErrorE",
+ "_ZN4lldb8SBHostOS13ThreadCreatedEPKc",
+};
+
+static constexpr llvm::StringRef ClassesWithoutDefaultCtor[] = {
+ "SBHostOS",
+ "SBReproducer",
+};
+
+static constexpr llvm::StringRef ClassesWithoutCopyOperations[] = {
+ "SBHostOS",
+ "SBReproducer",
+ "SBStream",
+ "SBProgress",
+};
+
+static constexpr llvm::StringRef MethodsWithPointerPlusLen[] = {
+ "_ZN4lldb6SBData11ReadRawDataERNS_7SBErrorEyPvm",
+ "_ZN4lldb6SBData7SetDataERNS_7SBErrorEPKvmNS_9ByteOrderEh",
+ "_ZN4lldb6SBData20SetDataWithOwnershipERNS_7SBErrorEPKvmNS_9ByteOrderEh",
+ "_ZN4lldb6SBData25CreateDataFromUInt64ArrayENS_9ByteOrderEjPym",
+ "_ZN4lldb6SBData25CreateDataFromUInt32ArrayENS_9ByteOrderEjPjm",
+ "_ZN4lldb6SBData25CreateDataFromSInt64ArrayENS_9ByteOrderEjPxm",
+ "_ZN4lldb6SBData25CreateDataFromSInt32ArrayENS_9ByteOrderEjPim",
+ "_ZN4lldb6SBData25CreateDataFromDoubleArrayENS_9ByteOrderEjPdm",
+ "_ZN4lldb6SBData22SetDataFromUInt64ArrayEPym",
+ "_ZN4lldb6SBData22SetDataFromUInt32ArrayEPjm",
+ "_ZN4lldb6SBData22SetDataFromSInt64ArrayEPxm",
+ "_ZN4lldb6SBData22SetDataFromSInt32ArrayEPim",
+ "_ZN4lldb6SBData22SetDataFromDoubleArrayEPdm",
+ "_ZN4lldb10SBDebugger22GetDefaultArchitectureEPcm",
+ "_ZN4lldb10SBDebugger13DispatchInputEPvPKvm",
+ "_ZN4lldb10SBDebugger13DispatchInputEPKvm",
+ "_ZN4lldb6SBFile4ReadEPhmPm",
+ "_ZN4lldb6SBFile5WriteEPKhmPm",
+ "_ZNK4lldb10SBFileSpec7GetPathEPcm",
+ "_ZN4lldb10SBFileSpec11ResolvePathEPKcPcm",
+ "_ZN4lldb8SBModule10GetVersionEPjj",
+ "_ZN4lldb12SBModuleSpec12SetUUIDBytesEPKhm",
+ "_ZNK4lldb9SBProcess9GetSTDOUTEPcm",
+ "_ZNK4lldb9SBProcess9GetSTDERREPcm",
+ "_ZNK4lldb9SBProcess19GetAsyncProfileDataEPcm",
+ "_ZN4lldb9SBProcess10ReadMemoryEyPvmRNS_7SBErrorE",
+ "_ZN4lldb9SBProcess11WriteMemoryEyPKvmRNS_7SBErrorE",
+ "_ZN4lldb9SBProcess21ReadCStringFromMemoryEyPvmRNS_7SBErrorE",
+ "_ZNK4lldb16SBStructuredData14GetStringValueEPcm",
+ "_ZN4lldb8SBTarget23BreakpointCreateByNamesEPPKcjjRKNS_"
+ "14SBFileSpecListES6_",
+ "_ZN4lldb8SBTarget10ReadMemoryENS_9SBAddressEPvmRNS_7SBErrorE",
+ "_ZN4lldb8SBTarget15GetInstructionsENS_9SBAddressEPKvm",
+ "_ZN4lldb8SBTarget25GetInstructionsWithFlavorENS_9SBAddressEPKcPKvm",
+ "_ZN4lldb8SBTarget15GetInstructionsEyPKvm",
+ "_ZN4lldb8SBTarget25GetInstructionsWithFlavorEyPKcPKvm",
+ "_ZN4lldb8SBThread18GetStopDescriptionEPcm",
+ // The below mangled names are used for dummy methods in shell tests
+ // that test the emitters' output. If you're adding any new mangled names
+ // from the actual SB API to this list please add them above.
+ "_ZN4lldb33SBRPC_"
+ "CHECKCONSTCHARPTRPTRWITHLEN27CheckConstCharPtrPtrWithLenEPPKcm",
+ "_ZN4lldb19SBRPC_CHECKARRAYPTR13CheckArrayPtrEPPKcm",
+ "_ZN4lldb18SBRPC_CHECKVOIDPTR12CheckVoidPtrEPvm",
+};
+
+// These methods should take a connection parameter according to our logic in
+// RequiresConnectionParameter() but in the handwritten version they
+// don't take a connection. These methods need to have their implementation
+// changed but for now, we just have an exception list of functions that will
+// never be a given connection parameter.
+//
+// FIXME: Change the implementation of these methods so that they can be given a
+// connection parameter.
+static constexpr llvm::StringRef
+ MethodsThatUnconditionallyDoNotNeedConnection[] = {
+ "_ZN4lldb16SBBreakpointNameC1ERNS_8SBTargetEPKc",
+ "_ZN4lldb10SBDebugger7DestroyERS0_",
+ "_ZN4lldb18SBExecutionContextC1ENS_8SBThreadE",
+};
+
+// These classes inherit from rpc::ObjectRef directly (as opposed to
+// rpc::LocalObjectRef). Changing them from ObjectRef to LocalObjectRef is ABI
+// breaking, so we preserve that compatibility here.
+//
+// lldb-rpc-gen emits classes as LocalObjectRefs by default.
+//
+// FIXME: Does it matter which one it emits by default?
+static constexpr llvm::StringRef ClassesThatInheritFromObjectRef[] = {
+ "SBAddress",
+ "SBBreakpointName",
+ "SBCommandInterpreter",
+ "SBCommandReturnObject",
+ "SBError",
+ "SBExecutionContext",
+ "SBExpressionOptions",
+ "SBFileSpec",
+ "SBFileSpecList",
+ "SBFormat",
+ "SBFunction",
+ "SBHistoricalFrame",
+ "SBHistoricalLineEntry",
+ "SBHistoricalLineEntryList",
+ "SBLineEntry",
+ "SBStream",
+ "SBStringList",
+ "SBStructuredData",
+ "SBSymbolContext",
+ "SBSymbolContextList",
+ "SBTypeMember",
+ "SBTypeSummaryOptions",
+ "SBValueList",
+};
+
+static llvm::StringMap<llvm::SmallVector<llvm::StringRef>>
+ ClassName_to_ParameterTypes = {
+ {"SBLaunchInfo", {"const char *"}},
+ {"SBPlatformConnectOptions", {"const char *"}},
+ {"SBPlatformShellCommand", {"const char *", "const char *"}},
+ {"SBBreakpointList", {"SBTarget"}},
+};
+
+QualType lldb_rpc_gen::GetUnderlyingType(QualType T) {
+ QualType UnderlyingType;
+ if (T->isPointerType())
+ UnderlyingType = T->getPointeeType();
+ else if (T->isReferenceType())
+ UnderlyingType = T.getNonReferenceType();
+ else
+ UnderlyingType = T;
+
+ return UnderlyingType;
+}
+
+QualType lldb_rpc_gen::GetUnqualifiedUnderlyingType(QualType T) {
+ QualType UnderlyingType = GetUnderlyingType(T);
+ return UnderlyingType.getUnqualifiedType();
+}
+
+std::string lldb_rpc_gen::GetMangledName(ASTContext &Context,
+ CXXMethodDecl *MDecl) {
+ std::string Mangled;
+ llvm::raw_string_ostream MangledStream(Mangled);
+
+ GlobalDecl GDecl;
+ if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(MDecl))
+ GDecl = GlobalDecl(CtorDecl, Ctor_Complete);
+ else if (const auto *DtorDecl = dyn_cast<CXXDestructorDecl>(MDecl))
+ GDecl = GlobalDecl(DtorDecl, Dtor_Deleting);
+ else
+ GDecl = GlobalDecl(MDecl);
+
+ MangleContext *MC = Context.createMangleContext();
+ MC->mangleName(GDecl, MangledStream);
+ return Mangled;
+}
+
+bool lldb_rpc_gen::TypeIsFromLLDBPrivate(QualType T) {
+
+ auto CheckTypeForLLDBPrivate = [](const Type *Ty) {
+ if (!Ty)
+ return false;
+ const auto *CXXRDecl = Ty->getAsCXXRecordDecl();
+ if (!CXXRDecl)
+ return false;
+ const auto *NSDecl =
+ llvm::dyn_cast<NamespaceDecl>(CXXRDecl->getDeclContext());
+ if (!NSDecl)
+ return false;
+ return NSDecl->getName() == "lldb_private";
+ };
+
+ // First, get the underlying type (remove qualifications and strip off any
+ // pointers/references). Then we'll need to desugar this type. This will
+ // remove things like typedefs, so instead of seeing "lldb::DebuggerSP" we'll
+ // actually see something like "std::shared_ptr<lldb_private::Debugger>".
+ QualType UnqualifiedUnderlyingType = GetUnqualifiedUnderlyingType(T);
+ const Type *DesugaredType =
+ UnqualifiedUnderlyingType->getUnqualifiedDesugaredType();
+ assert(DesugaredType && "DesugaredType from a valid Type is nullptr!");
+
+ // Check the type itself.
+ if (CheckTypeForLLDBPrivate(DesugaredType))
+ return true;
+
+ // If that didn't work, it's possible that the type has a template argument
+ // that is an lldb_private type.
+ if (const auto *TemplateSDecl =
+ llvm::dyn_cast_or_null<ClassTemplateSpecializationDecl>(
+ DesugaredType->getAsCXXRecordDecl())) {
+ for (const TemplateArgument &TA :
+ TemplateSDecl->getTemplateArgs().asArray()) {
+ if (TA.getKind() != TemplateArgument::Type)
+ continue;
+ if (CheckTypeForLLDBPrivate(TA.getAsType().getTypePtr()))
+ return true;
+ }
+ }
+ return false;
+}
+
+bool lldb_rpc_gen::TypeIsSBClass(QualType T) {
+ QualType UnqualifiedUnderlyingType = GetUnqualifiedUnderlyingType(T);
+ const auto *CXXRDecl = UnqualifiedUnderlyingType->getAsCXXRecordDecl();
+ if (!CXXRDecl)
+ return false; // SB Classes are always C++ classes
+
+ return CXXRDecl->getName().starts_with("SB");
+}
+
+bool lldb_rpc_gen::TypeIsConstCharPtr(QualType T) {
+ if (!T->isPointerType())
+ return false;
+
+ QualType UnderlyingType = T->getPointeeType();
+ if (!UnderlyingType.isConstQualified())
+ return false;
+
+ // FIXME: We should be able to do `UnderlyingType->isCharType` but that will
+ // return true for `const uint8_t *` since that is effectively an unsigned
+ // char pointer. We currently do not support pointers other than `const char
+ // *` and `const char **`.
+ return UnderlyingType->isSpecificBuiltinType(BuiltinType::Char_S) ||
+ UnderlyingType->isSpecificBuiltinType(BuiltinType::SChar);
+}
+
+bool lldb_rpc_gen::TypeIsConstCharPtrPtr(QualType T) {
+ if (!T->isPointerType())
+ return false;
+
+ return TypeIsConstCharPtr(T->getPointeeType());
+}
+
+bool lldb_rpc_gen::TypeIsDisallowedClass(QualType T) {
+ QualType UUT = GetUnqualifiedUnderlyingType(T);
+ const auto *CXXRDecl = UUT->getAsCXXRecordDecl();
+ if (!CXXRDecl)
+ return false;
+
+ llvm::StringRef DeclName = CXXRDecl->getName();
+ for (const llvm::StringRef DisallowedClass : DisallowedClasses)
+ if (DeclName == DisallowedClass)
+ return true;
+ return false;
+}
+
+bool lldb_rpc_gen::TypeIsCallbackFunctionPointer(QualType T) {
+ return T->isFunctionPointerType();
+}
+
+bool lldb_rpc_gen::MethodIsDisallowed(const std::string &MangledName) {
+ llvm::StringRef MangledNameRef(MangledName);
+ return llvm::is_contained(DisallowedMethods, MangledNameRef);
+}
+
+bool lldb_rpc_gen::HasCallbackParameter(CXXMethodDecl *MDecl) {
+ 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;
+ }
+
+ if ((*Iter)->getType()->isVoidPointerType())
+ HasBatonParameter = true;
+ }
+
+ return HasCallbackParameter && HasBatonParameter;
+}
+
+// FIXME: Find a better way to do this. Here is why it is written this way:
+// By the time we have already created a `Method` object, we have extracted the
+// `QualifiedName` and the relevant QualTypes for parameters/return types, many
+// of which contains "lldb::" in them. To change it in a way that would be
+// friendly to liblldbrpc, we would need to have a way of replacing that
+// namespace at the time of creating a Method, and only for liblldbrpc methods.
+// IMO this would complicate Method more than what I'm doing here, and not
+// necessarily for any more benefit.
+// In clang-tools-extra, there is a ChangeNamespaces tool which tries to do
+// something similar to this. It also operates primarily on string replacement,
+// but uses more sophisticated clang tooling to do so.
+// For now, this will do what we need it to do.
+std::string
+lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace(std::string Name) {
+ auto Pos = Name.find("lldb::");
+ while (Pos != std::string::npos) {
+ constexpr size_t SizeOfLLDBNamespace = 4;
+ Name.replace(Pos, SizeOfLLDBNamespace, "lldb_rpc");
+ Pos = Name.find("lldb::");
+ }
+ return Name;
+}
+
+std::string lldb_rpc_gen::StripLLDBNamespace(std::string Name) {
+ auto Pos = Name.find("lldb::");
+ if (Pos != std::string::npos) {
+ constexpr size_t SizeOfLLDBNamespace = 6;
+ Name = Name.substr(Pos + SizeOfLLDBNamespace);
+ }
+ return Name;
+}
+
+bool lldb_rpc_gen::SBClassRequiresDefaultCtor(const std::string &ClassName) {
+ return !llvm::is_contained(ClassesWithoutDefaultCtor, ClassName);
+}
+
+bool lldb_rpc_gen::SBClassRequiresCopyCtorAssign(const std::string &ClassName) {
+ return !llvm::is_contained(ClassesWithoutCopyOperations, ClassName);
+}
+
+bool lldb_rpc_gen::SBClassInheritsFromObjectRef(const std::string &ClassName) {
+ return llvm::is_contained(ClassesThatInheritFromObjectRef, ClassName);
+}
+
+std::string lldb_rpc_gen::GetSBClassNameFromType(QualType T) {
+ assert(lldb_rpc_gen::TypeIsSBClass(T) &&
+ "Cannot get SBClass name from non-SB class type!");
+
+ QualType UnqualifiedUnderlyingType = GetUnqualifiedUnderlyingType(T);
+ const auto *CXXRDecl = UnqualifiedUnderlyingType->getAsCXXRecordDecl();
+ assert(CXXRDecl && "SB class was not CXXRecordDecl!");
+ if (!CXXRDecl)
+ return std::string();
+
+ return CXXRDecl->getName().str();
+}
+lldb_rpc_gen::Method::Method(CXXMethodDecl *MDecl, const PrintingPolicy &Policy,
+ ASTContext &Context)
+ : Policy(Policy), Context(Context),
+ QualifiedName(MDecl->getQualifiedNameAsString()),
+ BaseName(MDecl->getNameAsString()),
+ MangledName(lldb_rpc_gen::GetMangledName(Context, MDecl)),
+ ReturnType(MDecl->getReturnType()), IsConst(MDecl->isConst()),
+ IsInstance(MDecl->isInstance()), IsCtor(isa<CXXConstructorDecl>(MDecl)),
+ IsCopyAssign(MDecl->isCopyAssignmentOperator()),
+ IsMoveAssign(MDecl->isMoveAssignmentOperator()),
+ IsDtor(isa<CXXDestructorDecl>(MDecl)),
+ IsConversionMethod(isa<CXXConversionDecl>(MDecl)) {
+ uint8_t UnnamedArgIdx = 0;
+ bool PrevParamWasPointer = false;
+ for (const auto *ParamDecl : MDecl->parameters()) {
+ Param param;
+ if (ParamDecl->hasDefaultArg())
+ param.DefaultValueText =
+ Lexer::getSourceText(
+ CharSourceRange::getTokenRange(
+ ParamDecl->getDefaultArg()->getSourceRange()),
+ Context.getSourceManager(), Context.getLangOpts())
+ .str();
+
+ param.IsFollowedByLen = false;
+ param.Name = ParamDecl->getNameAsString();
+ // If the parameter has no name, we'll generate one
+ if (param.Name.empty()) {
+ param.Name = "arg" + std::to_string(UnnamedArgIdx);
+ UnnamedArgIdx++;
+ }
+ param.Type = ParamDecl->getType();
+
+ // FIXME: Instead of using this heuristic, the ideal thing would be to add
+ // annotations to the SBAPI methods themselves. For now, we have a list of
+ // methods that we know will need this.
+ if (PrevParamWasPointer) {
+ PrevParamWasPointer = false;
+ const bool IsIntegerType = param.Type->isIntegerType() &&
+ !param.Type->isBooleanType() &&
+ !param.Type->isEnumeralType();
+ if (IsIntegerType && llvm::is_contained(MethodsWithPointerPlusLen,
+ llvm::StringRef(MangledName)))
+ Params.back().IsFollowedByLen = true;
+ }
+
+ if (param.Type->isPointerType() &&
+ !lldb_rpc_gen::TypeIsConstCharPtr(param.Type) &&
+ !param.Type->isFunctionPointerType())
+ PrevParamWasPointer = true;
+
+ if (param.Type->isFunctionPointerType())
+ ContainsFunctionPointerParameter = true;
+
+ Params.push_back(param);
+ }
+
+ if (IsInstance)
+ ThisType = MDecl->getThisType();
+
+ if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(MDecl)) {
+ IsExplicitCtorOrConversionMethod = CtorDecl->isExplicit();
+ IsCopyCtor = CtorDecl->isCopyConstructor();
+ IsMoveCtor = CtorDecl->isMoveConstructor();
+ } else if (const auto *ConversionDecl = dyn_cast<CXXConversionDecl>(MDecl))
+ IsExplicitCtorOrConversionMethod = ConversionDecl->isExplicit();
+}
+
+bool lldb_rpc_gen::Method::operator<(const lldb_rpc_gen::Method &rhs) const {
+ return this < &rhs;
+}
+
+std::string
+lldb_rpc_gen::Method::CreateParamListAsString(GenerationKind Generation,
+ bool IncludeDefaultValue) const {
+ assert((!IncludeDefaultValue || Generation == eLibrary) &&
+ "Default values should only be emitted on the library side!");
+
+ std::vector<std::string> ParamList;
+
+ if (Generation == eLibrary && RequiresConnectionParameter())
+ ParamList.push_back("const rpc::Connection &connection");
+
+ for (const auto &Param : Params) {
+ std::string ParamString;
+ llvm::raw_string_ostream ParamStringStream(ParamString);
+
+ if (Generation == eLibrary)
+ ParamStringStream << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace(
+ Param.Type.getAsString(Policy));
+ else
+ ParamStringStream << Param.Type.getAsString(Policy);
+
+ ParamStringStream << " " << Param.Name;
+ if (IncludeDefaultValue && Generation == eLibrary &&
+ !Param.DefaultValueText.empty())
+ ParamStringStream << " = "
+ << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace(
+ Param.DefaultValueText);
+
+ ParamList.push_back(ParamString);
+ }
+
+ return llvm::join(ParamList, ", ");
+}
+
+bool lldb_rpc_gen::Method::RequiresConnectionParameter() const {
+ if (llvm::is_contained(MethodsThatUnconditionallyDoNotNeedConnection,
+ MangledName)) {
+ return false;
+ }
+ if (!IsCtor && IsInstance)
+ return false;
+ if (IsCopyCtor || IsMoveCtor)
+ return false;
+ for (const auto &Param : Params)
+ // We can re-use the connection from our parameter if possible.
+ // Const-qualified parameters are input parameters and already
+ // have a valid connection to provide to the current method.
+ if (TypeIsSBClass(Param.Type) &&
+ GetUnderlyingType(Param.Type).isConstQualified())
+ return false;
+
+ return true;
+}
+
+std::string lldb_rpc_gen::GetDefaultArgumentsForConstructor(
+ std::string ClassName, const lldb_rpc_gen::Method &method) {
+
+ std::string ParamString;
+
+ const llvm::SmallVector<llvm::StringRef> &ParamTypes =
+ ClassName_to_ParameterTypes[ClassName];
+ std::vector<std::string> Params;
+
+ Params.push_back("connection_sp");
+ for (auto &ParamType : ParamTypes) {
+ if (ParamType == "const char *")
+ Params.push_back("nullptr");
+ else if (ParamType == "bool")
+ Params.push_back("false");
+ else if (ParamType.starts_with("SB")) {
+ // If the class to construct takes an SB parameter,
+ // go over the parameters from the method itself and
+ // see if it one of its parameters is that SB class.
+ // If not, see if we can use the method's class itself.
+ for (auto &CallingMethodParam : method.Params) {
+ QualType UUT = GetUnqualifiedUnderlyingType(CallingMethodParam.Type);
+ if (UUT.getAsString() == ParamType) {
+ Params.push_back(CallingMethodParam.Name);
+ } else if (GetSBClassNameFromType(method.ThisType) == ParamType) {
+ Params.push_back("*this");
+ break;
+ }
+ }
+ }
+ }
+
+ ParamString = llvm::join(Params, ", ");
+ return ParamString;
+}
diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.h b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.h
new file mode 100644
index 0000000000000..a7c96ad080bc4
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.h
@@ -0,0 +1,105 @@
+//===-- RPCCommon.h -------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_RPC_GEN_RPCCOMMON_H
+#define LLDB_RPC_GEN_RPCCOMMON_H
+
+#include "clang/AST/AST.h"
+#include "llvm/Support/ToolOutputFile.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <string>
+
+using namespace clang;
+
+namespace lldb_rpc_gen {
+QualType GetUnderlyingType(QualType T);
+QualType GetUnqualifiedUnderlyingType(QualType T);
+std::string GetMangledName(ASTContext &Context, CXXMethodDecl *MDecl);
+
+bool TypeIsFromLLDBPrivate(QualType T);
+bool TypeIsSBClass(QualType T);
+bool TypeIsConstCharPtr(QualType T);
+bool TypeIsConstCharPtrPtr(QualType T);
+bool TypeIsDisallowedClass(QualType T);
+bool TypeIsCallbackFunctionPointer(QualType T);
+
+bool MethodIsDisallowed(const std::string &MangledName);
+bool HasCallbackParameter(CXXMethodDecl *MDecl);
+
+std::string ReplaceLLDBNamespaceWithRPCNamespace(std::string Name);
+std::string StripLLDBNamespace(std::string Name);
+bool SBClassRequiresDefaultCtor(const std::string &ClassName);
+bool SBClassRequiresCopyCtorAssign(const std::string &ClassName);
+bool SBClassInheritsFromObjectRef(const std::string &ClassName);
+std::string GetSBClassNameFromType(QualType T);
+struct Param {
+ std::string Name;
+ QualType Type;
+ std::string DefaultValueText;
+ bool IsFollowedByLen;
+};
+
+enum GenerationKind : bool { eServer, eLibrary };
+
+struct Method {
+ enum Type { eOther, eConstructor, eDestructor };
+
+ Method(CXXMethodDecl *MDecl, const PrintingPolicy &Policy,
+ ASTContext &Context);
+
+ // Adding a '<' allows us to use Methods in ordered containers.
+ bool operator<(const lldb_rpc_gen::Method &rhs) const;
+ const PrintingPolicy &Policy;
+ const ASTContext &Context;
+ std::string QualifiedName;
+ std::string BaseName;
+ std::string MangledName;
+ QualType ReturnType;
+ QualType ThisType;
+ std::vector<Param> Params;
+ bool IsConst = false;
+ bool IsInstance = false;
+ bool IsCtor = false;
+ bool IsCopyCtor = false;
+ bool IsCopyAssign = false;
+ bool IsMoveCtor = false;
+ bool IsMoveAssign = false;
+ bool IsDtor = false;
+ bool IsConversionMethod = false;
+ bool IsExplicitCtorOrConversionMethod = false;
+ bool ContainsFunctionPointerParameter = false;
+
+ std::string CreateParamListAsString(GenerationKind Generation,
+ bool IncludeDefaultValue = false) const;
+
+ bool RequiresConnectionParameter() const;
+};
+
+std::string
+GetDefaultArgumentsForConstructor(std::string ClassName,
+ const lldb_rpc_gen::Method &method);
+
+class FileEmitter {
+protected:
+ FileEmitter(std::unique_ptr<llvm::ToolOutputFile> &&OutputFile)
+ : OutputFile(std::move(OutputFile)), IndentLevel(0) {}
+ void EmitLine(const std::string &line) {
+ for (auto i = 0; i < IndentLevel; i++)
+ OutputFile->os() << " ";
+
+ OutputFile->os() << line << "\n";
+ }
+
+ void EmitNewLine() { OutputFile->os() << "\n"; }
+
+ std::unique_ptr<llvm::ToolOutputFile> OutputFile;
+ uint8_t IndentLevel;
+};
+} // namespace lldb_rpc_gen
+#endif // LLDB_RPC_GEN_RPCCOMMON_H
diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerHeaderEmitter.cpp b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerHeaderEmitter.cpp
new file mode 100644
index 0000000000000..a2818ada1f323
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerHeaderEmitter.cpp
@@ -0,0 +1,73 @@
+//===-- RPCServerHeaderEmitter.cpp ----------------------------------------===//
+//
+// 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 "RPCServerHeaderEmitter.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 RPCServerHeaderEmitter::EmitMethod(const Method &method) {
+ const std::string &MangledName = method.MangledName;
+
+ EmitLine("class " + MangledName +
+ " : public rpc_common::RPCFunctionInstance {");
+ EmitLine("public:");
+ IndentLevel++;
+ EmitConstructor(MangledName);
+ EmitDestructor(MangledName);
+ EmitHandleRPCCall();
+ IndentLevel--;
+ EmitLine("};");
+}
+
+void RPCServerHeaderEmitter::EmitHandleRPCCall() {
+ EmitLine("bool HandleRPCCall(rpc_common::Connection &connection, "
+ "rpc_common::RPCStream &send, rpc_common::RPCStream &response) "
+ "override;");
+}
+
+void RPCServerHeaderEmitter::EmitConstructor(const std::string &MangledName) {
+ EmitLine(MangledName + "() : RPCFunctionInstance(\"" + MangledName +
+ "\") {}");
+}
+
+void RPCServerHeaderEmitter::EmitDestructor(const std::string &MangledName) {
+ EmitLine("~" + MangledName + "() override {}");
+}
+
+std::string RPCServerHeaderEmitter::GetHeaderGuard() {
+ const std::string UpperFilenameNoExt =
+ llvm::sys::path::stem(
+ llvm::sys::path::filename(OutputFile->getFilename()))
+ .upper();
+ return "GENERATED_LLDB_RPC_SERVER_" + UpperFilenameNoExt + "_H";
+}
+
+void RPCServerHeaderEmitter::Begin() {
+ const std::string HeaderGuard = GetHeaderGuard();
+ EmitLine("#ifndef " + HeaderGuard);
+ EmitLine("#define " + HeaderGuard);
+ EmitLine("");
+ EmitLine("#include <lldb-rpc/common/RPCFunction.h>");
+ EmitLine("");
+ EmitLine("namespace rpc_server {");
+}
+
+void RPCServerHeaderEmitter::End() {
+ EmitLine("} // namespace rpc_server");
+ EmitLine("#endif // " + GetHeaderGuard());
+}
diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerHeaderEmitter.h b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerHeaderEmitter.h
new file mode 100644
index 0000000000000..cdf201090b7c8
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerHeaderEmitter.h
@@ -0,0 +1,46 @@
+//===-- RPCServerHeaderEmitter.h ----------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_RPC_GEN_RPCSERVERHEADEREMITTER_H
+#define LLDB_RPC_GEN_RPCSERVERHEADEREMITTER_H
+
+#include "RPCCommon.h"
+
+#include "clang/AST/AST.h"
+#include "llvm/Support/ToolOutputFile.h"
+
+using namespace clang;
+
+namespace lldb_rpc_gen {
+class RPCServerHeaderEmitter : public FileEmitter {
+public:
+ RPCServerHeaderEmitter(std::unique_ptr<llvm::ToolOutputFile> &&OutputFile)
+ : FileEmitter(std::move(OutputFile)) {
+ Begin();
+ }
+
+ ~RPCServerHeaderEmitter() { End(); }
+
+ void EmitMethod(const Method &method);
+
+private:
+ void EmitHandleRPCCall();
+
+ void EmitConstructor(const std::string &MangledName);
+
+ void EmitDestructor(const std::string &MangledName);
+
+ std::string GetHeaderGuard();
+
+ void Begin();
+
+ void End();
+};
+} // namespace lldb_rpc_gen
+
+#endif // LLDB_RPC_GEN_RPCSERVERHEADEREMITTER_H
diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerSourceEmitter.cpp b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerSourceEmitter.cpp
new file mode 100644
index 0000000000000..645ddd11662a7
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerSourceEmitter.cpp
@@ -0,0 +1,592 @@
+//===-- RPCServerSourceEmitter.cpp ----------------------------------------===//
+//
+// 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 "RPCServerSourceEmitter.h"
+#include "RPCCommon.h"
+
+#include "clang/AST/AST.h"
+#include "clang/Frontend/CompilerInstance.h"
+
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/ToolOutputFile.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <map>
+
+using namespace clang;
+using namespace lldb_rpc_gen;
+
+// For methods with pointer return types, it's important that we know how big
+// the type of the pointee is. We must correctly size a buffer (in the form of a
+// Bytes object) before we can actually use it.
+static const std::map<llvm::StringRef, size_t> MethodsWithPointerReturnTypes = {
+ {"_ZN4lldb12SBModuleSpec12GetUUIDBytesEv", 16}, // sizeof(uuid_t) -> 16
+ {"_ZNK4lldb8SBModule12GetUUIDBytesEv", 16}, // sizeof(uuid_t) -> 16
+};
+
+void RPCServerSourceEmitter::EmitMethod(const Method &method) {
+ if (method.ContainsFunctionPointerParameter)
+ EmitCallbackFunction(method);
+
+ EmitCommentHeader(method);
+ EmitFunctionHeader(method);
+ EmitFunctionBody(method);
+ EmitFunctionFooter();
+}
+
+void RPCServerSourceEmitter::EmitCommentHeader(const Method &method) {
+ std::string CommentLine;
+ llvm::raw_string_ostream CommentStream(CommentLine);
+
+ CommentStream << "// " << method.QualifiedName << "("
+ << method.CreateParamListAsString(eServer) << ")";
+ if (method.IsConst)
+ CommentStream << " const";
+
+ EmitLine("//------------------------------------------------------------");
+ EmitLine(CommentLine);
+ EmitLine("//------------------------------------------------------------");
+}
+
+void RPCServerSourceEmitter::EmitFunctionHeader(const Method &method) {
+ std::string FunctionHeader;
+ llvm::raw_string_ostream FunctionHeaderStream(FunctionHeader);
+ FunctionHeaderStream
+ << "bool rpc_server::" << method.MangledName
+ << "::HandleRPCCall(rpc_common::Connection &connection, RPCStream "
+ "&send, RPCStream &response) {";
+ EmitLine(FunctionHeader);
+ IndentLevel++;
+}
+
+void RPCServerSourceEmitter::EmitFunctionBody(const Method &method) {
+ EmitLine("// 1) Make local storage for incoming function arguments");
+ EmitStorageForParameters(method);
+ EmitLine("// 2) Decode all function arguments");
+ EmitDecodeForParameters(method);
+ EmitLine("// 3) Call the method and encode the return value");
+ EmitMethodCallAndEncode(method);
+}
+
+void RPCServerSourceEmitter::EmitFunctionFooter() {
+ EmitLine("return true;");
+ IndentLevel--;
+ EmitLine("}");
+}
+
+void RPCServerSourceEmitter::EmitStorageForParameters(const Method &method) {
+ // If we have an instance method and it isn't a constructor, we'll need to
+ // emit a "this" pointer.
+ if (method.IsInstance && !method.IsCtor)
+ EmitStorageForOneParameter(method.ThisType, "this_ptr", method.Policy,
+ /* IsFollowedByLen = */ false);
+ for (auto Iter = method.Params.begin(); Iter != method.Params.end(); Iter++) {
+ EmitStorageForOneParameter(Iter->Type, Iter->Name, method.Policy,
+ Iter->IsFollowedByLen);
+ // Skip over the length parameter, we don't emit it.
+ if (!lldb_rpc_gen::TypeIsConstCharPtrPtr(Iter->Type) &&
+ Iter->IsFollowedByLen)
+ Iter++;
+ }
+}
+
+void RPCServerSourceEmitter::EmitStorageForOneParameter(
+ QualType ParamType, const std::string &ParamName,
+ const PrintingPolicy &Policy, bool IsFollowedByLen) {
+ // First, we consider `const char *`, `const char **`. They have special
+ // server-side types.
+ if (TypeIsConstCharPtr(ParamType)) {
+ EmitLine("rpc_common::ConstCharPointer " + ParamName + ";");
+ return;
+ } else if (TypeIsConstCharPtrPtr(ParamType)) {
+ EmitLine("rpc_common::StringList " + ParamName + ";");
+ return;
+ }
+
+ QualType UnderlyingType =
+ lldb_rpc_gen::GetUnqualifiedUnderlyingType(ParamType);
+ const bool IsSBClass = lldb_rpc_gen::TypeIsSBClass(UnderlyingType);
+
+ if (ParamType->isPointerType() && !IsSBClass) {
+ // Void pointer with no length is usually a baton for a callback. We're
+ // going to hold onto the pointer value so we can send it back to the
+ // client-side when we implement callbacks.
+ if (ParamType->isVoidPointerType() && !IsFollowedByLen) {
+ EmitLine("void * " + ParamName + " = nullptr;");
+ return;
+ }
+
+ if (!ParamType->isFunctionPointerType()) {
+ EmitLine("Bytes " + ParamName + ";");
+ return;
+ }
+
+ assert(ParamType->isFunctionPointerType() && "Unhandled pointer type");
+ EmitLine("rpc_common::function_ptr_t " + ParamName + " = nullptr;");
+ return;
+ }
+
+ std::string StorageDeclaration;
+ llvm::raw_string_ostream StorageDeclarationStream(StorageDeclaration);
+
+ UnderlyingType.print(StorageDeclarationStream, Policy);
+ StorageDeclarationStream << " ";
+ if (IsSBClass)
+ StorageDeclarationStream << "*";
+ StorageDeclarationStream << ParamName;
+ if (IsSBClass)
+ StorageDeclarationStream << " = nullptr";
+ else
+ StorageDeclarationStream << " = {}";
+ StorageDeclarationStream << ";";
+ EmitLine(StorageDeclaration);
+}
+
+void RPCServerSourceEmitter::EmitDecodeForParameters(const Method &method) {
+ if (method.IsInstance && !method.IsCtor)
+ EmitDecodeForOneParameter(method.ThisType, "this_ptr", method.Policy);
+ for (auto Iter = method.Params.begin(); Iter != method.Params.end(); Iter++) {
+ EmitDecodeForOneParameter(Iter->Type, Iter->Name, method.Policy);
+ if (!lldb_rpc_gen::TypeIsConstCharPtrPtr(Iter->Type) &&
+ Iter->IsFollowedByLen)
+ Iter++;
+ }
+}
+
+void RPCServerSourceEmitter::EmitDecodeForOneParameter(
+ QualType ParamType, const std::string &ParamName,
+ const PrintingPolicy &Policy) {
+ QualType UnderlyingType =
+ lldb_rpc_gen::GetUnqualifiedUnderlyingType(ParamType);
+
+ if (TypeIsSBClass(UnderlyingType)) {
+ std::string DecodeLine;
+ llvm::raw_string_ostream DecodeLineStream(DecodeLine);
+ DecodeLineStream << ParamName << " = "
+ << "RPCServerObjectDecoder<";
+ UnderlyingType.print(DecodeLineStream, Policy);
+ DecodeLineStream << ">(send, rpc_common::RPCPacket::ValueType::Argument);";
+ EmitLine(DecodeLine);
+ EmitLine("if (!" + ParamName + ")");
+ IndentLevel++;
+ EmitLine("return false;");
+ IndentLevel--;
+ } else {
+ EmitLine("if (!RPCValueDecoder(send, "
+ "rpc_common::RPCPacket::ValueType::Argument, " +
+ ParamName + "))");
+ IndentLevel++;
+ EmitLine("return false;");
+ IndentLevel--;
+ }
+}
+
+std::string RPCServerSourceEmitter::CreateMethodCall(const Method &method) {
+ std::string MethodCall;
+ llvm::raw_string_ostream MethodCallStream(MethodCall);
+ if (method.IsInstance) {
+ if (!method.IsCtor)
+ MethodCallStream << "this_ptr->";
+ MethodCallStream << method.BaseName;
+ } else
+ MethodCallStream << method.QualifiedName;
+
+ std::vector<std::string> Args;
+ std::string FunctionPointerName;
+ for (auto Iter = method.Params.begin(); Iter != method.Params.end(); Iter++) {
+ std::string Arg;
+ // We must check for `const char *` and `const char **` first.
+ if (TypeIsConstCharPtr(Iter->Type)) {
+ // `const char *` is stored server-side as rpc_common::ConstCharPointer
+ Arg = Iter->Name + ".c_str()";
+ } else if (TypeIsConstCharPtrPtr(Iter->Type)) {
+ // `const char **` is stored server-side as rpc_common::StringList
+ Arg = Iter->Name + ".argv()";
+ } else if (lldb_rpc_gen::TypeIsSBClass(Iter->Type)) {
+ Arg = Iter->Name;
+ if (!Iter->Type->isPointerType())
+ Arg = "*" + Iter->Name;
+ } else if (Iter->Type->isPointerType() &&
+ !Iter->Type->isFunctionPointerType() &&
+ (!Iter->Type->isVoidPointerType() || Iter->IsFollowedByLen)) {
+ // We move pointers between the server and client as 'Bytes' objects.
+ // Pointers with length arguments will have their length filled in below.
+ // Pointers with no length arguments are assumed to behave like an array
+ // with length of 1, except for void pointers which are handled
+ // differently.
+ Arg = "(" + Iter->Type.getAsString(method.Policy) + ")" + Iter->Name +
+ ".GetData()";
+ } else if (Iter->Type->isFunctionPointerType()) {
+ // If we have a function pointer, we only want to pass something along if
+ // we got a real pointer.
+ Arg = Iter->Name + " ? " + method.MangledName + "_callback : nullptr";
+ FunctionPointerName = Iter->Name;
+ } else if (Iter->Type->isVoidPointerType() && !Iter->IsFollowedByLen &&
+ method.ContainsFunctionPointerParameter) {
+ // Assumptions:
+ // - This is assumed to be the baton for the function pointer.
+ // - This is assumed to come after the function pointer parameter.
+ // We always produce this regardless of the value of the baton argument.
+ Arg = "new CallbackInfo(" + FunctionPointerName + ", " + Iter->Name +
+ ", connection.GetConnectionID())";
+ } else
+ Arg = Iter->Name;
+
+ if (Iter->Type->isRValueReferenceType())
+ Arg = "std::move(" + Arg + ")";
+ Args.push_back(Arg);
+
+ if (!lldb_rpc_gen::TypeIsConstCharPtrPtr(Iter->Type) &&
+ Iter->IsFollowedByLen) {
+ std::string LengthArg = Iter->Name + ".GetSize()";
+ if (!Iter->Type->isVoidPointerType()) {
+ QualType UUT = lldb_rpc_gen::GetUnqualifiedUnderlyingType(Iter->Type);
+ LengthArg += " / sizeof(" + UUT.getAsString(method.Policy) + ")";
+ }
+ Args.push_back(LengthArg);
+ Iter++;
+ }
+ }
+ MethodCallStream << "(" << llvm::join(Args, ", ") << ")";
+
+ return MethodCall;
+}
+
+std::string RPCServerSourceEmitter::CreateEncodeLine(const std::string &Value,
+ bool IsEncodingSBClass) {
+ std::string EncodeLine;
+ llvm::raw_string_ostream EncodeLineStream(EncodeLine);
+
+ if (IsEncodingSBClass)
+ EncodeLineStream << "RPCServerObjectEncoder(";
+ else
+ EncodeLineStream << "RPCValueEncoder(";
+
+ EncodeLineStream
+ << "response, rpc_common::RPCPacket::ValueType::ReturnValue, ";
+ EncodeLineStream << Value;
+ EncodeLineStream << ");";
+ return EncodeLine;
+}
+
+// There are 4 cases to consider:
+// - const SBClass &: No need to do anything.
+// - const foo &: No need to do anything.
+// - SBClass &: The server and the client hold on to IDs to refer to specific
+// instances, so there's no need to send any information back to the client.
+// - foo &: The client is sending us a value over the wire, but because the type
+// is mutable, we must send the changed value back in case the method call
+// mutated it.
+//
+// Updating a mutable reference is done as a return value from the RPC
+// perspective. These return values need to be emitted after the method's return
+// value, and they are emitted in the order in which they occur in the
+// declaration.
+void RPCServerSourceEmitter::EmitEncodesForMutableParameters(
+ const std::vector<Param> &Params) {
+ for (auto Iter = Params.begin(); Iter != Params.end(); Iter++) {
+ // No need to manually update an SBClass
+ if (lldb_rpc_gen::TypeIsSBClass(Iter->Type))
+ continue;
+
+ if (!Iter->Type->isReferenceType() && !Iter->Type->isPointerType())
+ continue;
+
+ // If we have a void pointer with no length, there's nothing to update. This
+ // is likely a baton for a callback. The same goes for function pointers.
+ if (Iter->Type->isFunctionPointerType() ||
+ (Iter->Type->isVoidPointerType() && !Iter->IsFollowedByLen))
+ continue;
+
+ // No need to update pointers and references to const-qualified data.
+ QualType UnderlyingType = lldb_rpc_gen::GetUnderlyingType(Iter->Type);
+ if (UnderlyingType.isConstQualified())
+ continue;
+
+ const std::string EncodeLine =
+ CreateEncodeLine(Iter->Name, /* IsEncodingSBClass = */ false);
+ EmitLine(EncodeLine);
+ }
+}
+
+// There are 3 possible scenarios that this method can encounter:
+// 1. The method has no return value and is not a constructor.
+// Only the method call itself is emitted.
+// 2. The method is a constructor.
+// The call to the constructor is emitted in the encode line.
+// 3. The method has a return value.
+// The method call is emitted and the return value is captured in a variable.
+// After that, an encode call is emitted with the variable that captured the
+// return value.
+void RPCServerSourceEmitter::EmitMethodCallAndEncode(const Method &method) {
+ // FIXME: The hand-written lldb-rpc-server currently doesn't emit destructors
+ // for LocalObjectRefs... even if the type is an ObjectRef. What should we do
+ // here?
+
+ const std::string MethodCall = CreateMethodCall(method);
+
+ // If this function returns nothing, we just emit the call and update any
+ // mutable references. Note that constructors have return type `void` so we
+ // must explicitly check for that here.
+ if (!method.IsCtor && method.ReturnType->isVoidType()) {
+ EmitLine(MethodCall + ";");
+ EmitEncodesForMutableParameters(method.Params);
+ return;
+ }
+
+ static constexpr llvm::StringLiteral ReturnVariableName("__result");
+
+ // If this isn't a constructor, we'll need to store the result of the method
+ // call in a result variable.
+ if (!method.IsCtor) {
+ // We need to determine what the appropriate return type is. Here is the
+ // strategy:
+ // 1.) `SBFoo` -> `SBFoo &&`
+ // 2.) If the type is a pointer other than `const char *` or `const char **`
+ // or `void *`, the return type will be `Bytes` (e.g. `const uint8_t *`
+ // -> `Bytes`).
+ // 3.) Otherwise, emit the exact same return type.
+ std::string ReturnTypeName;
+ std::string AssignLine;
+ llvm::raw_string_ostream AssignLineStream(AssignLine);
+ if (method.ReturnType->isPointerType() &&
+ !lldb_rpc_gen::TypeIsConstCharPtr(method.ReturnType) &&
+ !lldb_rpc_gen::TypeIsConstCharPtrPtr(method.ReturnType) &&
+ !method.ReturnType->isVoidPointerType()) {
+ llvm::StringRef MangledNameRef(method.MangledName);
+ auto Pos = MethodsWithPointerReturnTypes.find(MangledNameRef);
+ assert(Pos != MethodsWithPointerReturnTypes.end() &&
+ "Unable to determine the size of the return buffer");
+ if (Pos == MethodsWithPointerReturnTypes.end()) {
+ EmitLine(
+ "// Intentionally inserting a compiler error. lldb-rpc-gen "
+ "was unable to determine how large the return buffer should be.");
+ EmitLine("ThisShouldNotCompile");
+ return;
+ }
+ AssignLineStream << "Bytes " << ReturnVariableName << "(" << MethodCall
+ << ", " << Pos->second << ");";
+ } else {
+ if (lldb_rpc_gen::TypeIsSBClass(method.ReturnType)) {
+ // We want to preserve constness, so we don't strip qualifications from
+ // the underlying type
+ QualType UnderlyingReturnType =
+ lldb_rpc_gen::GetUnderlyingType(method.ReturnType);
+ ReturnTypeName =
+ UnderlyingReturnType.getAsString(method.Policy) + " &&";
+ } else
+ ReturnTypeName = method.ReturnType.getAsString(method.Policy);
+
+ AssignLineStream << ReturnTypeName << " " << ReturnVariableName << " = "
+ << MethodCall << ";";
+ }
+ EmitLine(AssignLine);
+ }
+
+ const bool IsEncodingSBClass =
+ lldb_rpc_gen::TypeIsSBClass(method.ReturnType) || method.IsCtor;
+
+ std::string ValueToEncode;
+ if (IsEncodingSBClass) {
+ if (method.IsCtor)
+ ValueToEncode = MethodCall;
+ else
+ ValueToEncode = "std::move(" + ReturnVariableName.str() + ")";
+ } else
+ ValueToEncode = ReturnVariableName.str();
+
+ const std::string ReturnValueEncodeLine =
+ CreateEncodeLine(ValueToEncode, IsEncodingSBClass);
+ EmitLine(ReturnValueEncodeLine);
+ EmitEncodesForMutableParameters(method.Params);
+}
+
+// NOTE: This contains most of the same knowledge as RPCLibrarySourceEmitter. I
+// have chosen not to re-use code here because the needs are different enough
+// that it would be more work to re-use than just reimplement portions of it.
+// Specifically:
+// - Callbacks do not neatly fit into a `Method` object, which currently
+// assumes that you have a CXXMethodDecl (We have a FunctionDecl at most).
+// - We only generate callbacks that have a `void *` baton parameter. We hijack
+// those baton parameters and treat them differently.
+// - Callbacks need to do something special for moving SB class references back
+// to the client-side.
+// FIXME: Figure out what we can actually re-use in a meaningful way between
+// this method and RPCLibrarySourceEmitter.
+void RPCServerSourceEmitter::EmitCallbackFunction(const Method &method) {
+ // Check invariants and locate necessary resources
+ Param FuncPointerParam;
+ Param BatonParam;
+ for (const auto &Param : method.Params)
+ if (Param.Type->isFunctionPointerType())
+ FuncPointerParam = Param;
+ else if (Param.Type->isVoidPointerType())
+ BatonParam = Param;
+
+ assert(FuncPointerParam.Type->isFunctionPointerType() &&
+ "Emitting callback function with no function pointer");
+ assert(BatonParam.Type->isVoidPointerType() &&
+ "Emitting callback function with no baton");
+
+ QualType FuncType = FuncPointerParam.Type->getPointeeType();
+ const auto *FuncProtoType = FuncType->getAs<FunctionProtoType>();
+ assert(FuncProtoType && "Emitting callback with no parameter information");
+ if (!FuncProtoType)
+ return; // If asserts are off, we'll just fail to compile.
+
+ std::vector<Param> CallbackParams;
+ std::vector<std::string> CallbackParamsAsStrings;
+ uint8_t ArgIdx = 0;
+ for (QualType ParamType : FuncProtoType->param_types()) {
+ Param CallbackParam;
+ CallbackParam.IsFollowedByLen = false;
+ CallbackParam.Type = ParamType;
+ if (ParamType->isVoidPointerType())
+ CallbackParam.Name = "baton";
+ else
+ CallbackParam.Name = "arg" + std::to_string(ArgIdx++);
+
+ CallbackParams.push_back(CallbackParam);
+ CallbackParamsAsStrings.push_back(ParamType.getAsString(method.Policy) +
+ " " + CallbackParam.Name);
+ }
+ const std::string CallbackReturnTypeName =
+ FuncProtoType->getReturnType().getAsString(method.Policy);
+ const std::string CallbackName = method.MangledName + "_callback";
+
+ // Emit Function Header
+ std::string Header;
+ llvm::raw_string_ostream HeaderStream(Header);
+ HeaderStream << "static " << CallbackReturnTypeName << " " << CallbackName
+ << "(" << llvm::join(CallbackParamsAsStrings, ", ") << ") {";
+ EmitLine(Header);
+ IndentLevel++;
+
+ // Emit Function Body
+ EmitLine("// RPC connection setup and sanity checking");
+ EmitLine("CallbackInfo *callback_info = (CallbackInfo *)baton;");
+ EmitLine("rpc_common::ConnectionSP connection_sp = "
+ "rpc_common::Connection::GetConnectionFromID(callback_info->"
+ "connection_id);");
+ EmitLine("if (!connection_sp)");
+ IndentLevel++;
+ if (FuncProtoType->getReturnType()->isVoidType())
+ EmitLine("return;");
+ else
+ EmitLine("return {};");
+ IndentLevel--;
+
+ EmitLine("// Preparing to make the call");
+ EmitLine("static RPCFunctionInfo g_func(\"" + CallbackName + "\");");
+ EmitLine("RPCStream send;");
+ EmitLine("RPCStream response;");
+ EmitLine("g_func.Encode(send);");
+
+ EmitLine("// The first thing we encode is the callback address so that the "
+ "client-side can know where the callback is");
+ EmitLine("RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, "
+ "callback_info->callback);");
+ EmitLine("// Encode all the arguments");
+ for (const Param &CallbackParam : CallbackParams) {
+ if (lldb_rpc_gen::TypeIsSBClass(CallbackParam.Type)) {
+
+ // FIXME: SB class server references are stored as non-const references so
+ // that we can actually change them as needed. If a parameter is marked
+ // const, we will fail to compile because we cannot make an
+ // SBFooServerReference from a `const SBFoo &`.
+ // To work around this issue, we'll apply a `const_cast` if needed so we
+ // can continue to generate callbacks for now, but we really should
+ // rethink the way we store object IDs server-side to support
+ // const-qualified parameters.
+ QualType UnderlyingSBClass =
+ lldb_rpc_gen::GetUnderlyingType(CallbackParam.Type);
+ QualType UnqualifiedUnderlyingSBClass =
+ UnderlyingSBClass.getUnqualifiedType();
+
+ // FIXME: Replace this bespoke logic with a nice function in RPCCommon.
+ std::string SBClassName =
+ UnqualifiedUnderlyingSBClass.getAsString(method.Policy);
+ llvm::StringRef SBClassNameRef(SBClassName);
+ SBClassNameRef.consume_front("lldb::");
+
+ std::string ServerReferenceLine;
+ llvm::raw_string_ostream ServerReferenceLineStream(ServerReferenceLine);
+ ServerReferenceLineStream << "rpc_server::" << SBClassNameRef
+ << "ServerReference " << CallbackParam.Name
+ << "_ref(";
+
+ if (UnderlyingSBClass.isConstQualified()) {
+ QualType NonConstSBType =
+ method.Context.getLValueReferenceType(UnqualifiedUnderlyingSBClass);
+ ServerReferenceLineStream << "const_cast<" << NonConstSBType << ">(";
+ }
+ ServerReferenceLineStream << CallbackParam.Name;
+ if (UnderlyingSBClass.isConstQualified())
+ ServerReferenceLineStream << ")";
+
+ ServerReferenceLineStream << ");";
+ EmitLine(ServerReferenceLine);
+ EmitLine(
+ CallbackParam.Name +
+ "_ref.Encode(send, rpc_common::RPCPacket::ValueType::Argument);");
+ } else {
+ std::string ParamName;
+ if (CallbackParam.Type->isVoidPointerType())
+ ParamName = "callback_info->baton";
+ else
+ ParamName = CallbackParam.Name;
+ EmitLine(
+ "RPCValueEncoder(send, rpc_common::RPCPacket::ValueType::Argument, " +
+ ParamName + ");");
+ }
+ }
+
+ if (!FuncProtoType->getReturnType()->isVoidType()) {
+ EmitLine("// Storage for return value");
+ const bool ReturnsSBClass =
+ lldb_rpc_gen::TypeIsSBClass(FuncProtoType->getReturnType());
+ std::string ReturnValueLine = CallbackReturnTypeName;
+ llvm::raw_string_ostream ReturnValueLineStream(ReturnValueLine);
+
+ if (ReturnsSBClass)
+ ReturnValueLineStream << " *";
+ ReturnValueLineStream << " __result = ";
+ if (ReturnsSBClass)
+ ReturnValueLineStream << "nullptr";
+ else
+ ReturnValueLineStream << "{}";
+ ReturnValueLineStream << ";";
+ EmitLine(ReturnValueLine);
+ }
+
+ EmitLine(
+ "if (connection_sp->SendRPCCallAndWaitForResponse(send, response)) {");
+ IndentLevel++;
+ if (!FuncProtoType->getReturnType()->isVoidType()) {
+ if (lldb_rpc_gen::TypeIsSBClass(FuncProtoType->getReturnType())) {
+ EmitLine("__result = rpc_server::RPCServerObjectDecoder<" +
+ CallbackReturnTypeName +
+ ">(response, rpc_common::RPCPacket::ValueType::ReturnValue);");
+ } else
+ EmitLine("RPCValueDecoder(response, "
+ "rpc_common::RPCPacket::ValueType::ReturnValue, __result);");
+ }
+ IndentLevel--;
+ EmitLine("}");
+ if (!FuncProtoType->getReturnType()->isVoidType()) {
+ if (lldb_rpc_gen::TypeIsSBClass(FuncProtoType->getReturnType()))
+ EmitLine("return *__result;");
+ else
+ EmitLine("return __result;");
+ }
+
+ // Emit Function Footer;
+ IndentLevel--;
+ EmitLine("};");
+}
diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerSourceEmitter.h b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerSourceEmitter.h
new file mode 100644
index 0000000000000..c154efa98dc0b
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCServerSourceEmitter.h
@@ -0,0 +1,80 @@
+//===-- RPCServerSourceEmitter.h ------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+#ifndef LLDB_RPC_GEN_RPCSERVERMETHODEMITTER_H
+#define LLDB_RPC_GEN_RPCSERVERMETHODEMITTER_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 RPCServerSourceEmitter : public FileEmitter {
+public:
+ RPCServerSourceEmitter(std::unique_ptr<llvm::ToolOutputFile> &&OutputFile)
+ : FileEmitter(std::move(OutputFile)) {
+ Begin();
+ }
+
+ /// Given a Method, emits a server-side implementation of the method
+ /// for lldb-rpc-server
+ void EmitMethod(const Method &method);
+
+private:
+ void EmitCommentHeader(const Method &method);
+
+ void EmitFunctionHeader(const Method &method);
+
+ void EmitFunctionBody(const Method &method);
+
+ void EmitFunctionFooter();
+
+ void EmitStorageForParameters(const Method &method);
+
+ void EmitStorageForOneParameter(QualType ParamType,
+ const std::string &ParamName,
+ const PrintingPolicy &Policy,
+ bool IsFollowedByLen);
+
+ void EmitDecodeForParameters(const Method &method);
+
+ void EmitDecodeForOneParameter(QualType ParamType,
+ const std::string &ParamName,
+ const PrintingPolicy &Policy);
+
+ std::string CreateMethodCall(const Method &method);
+
+ std::string CreateEncodeLine(const std::string &value,
+ bool IsEncodingSBClass);
+
+ void EmitEncodesForMutableParameters(const std::vector<Param> &Params);
+
+ void EmitMethodCallAndEncode(const Method &method);
+
+ void EmitCallbackFunction(const Method &method);
+
+ void Begin() {
+ EmitLine("#include \"RPCUserServer.h\"");
+ EmitLine("#include \"SBAPI.h\"");
+ EmitLine("#include <lldb-rpc/common/RPCArgument.h>");
+ EmitLine("#include <lldb-rpc/common/RPCCommon.h>");
+ EmitLine("#include <lldb-rpc/common/RPCFunction.h>");
+ EmitLine("#include <lldb/API/LLDB.h>");
+ EmitLine("");
+ EmitLine("using namespace rpc_common;");
+ EmitLine("using namespace lldb;");
+ }
+};
+} // namespace lldb_rpc_gen
+
+#endif // LLDB_RPC_GEN_RPCSERVERMETHODEMITTER_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..81ef6594136c6
--- /dev/null
+++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp
@@ -0,0 +1,540 @@
+#include "RPCBindingsHarnessEmitter.h"
+#include "RPCClientCallbacksSourceEmitter.h"
+#include "RPCCommon.h"
+#include "RPCLibraryHeaderEmitter.h"
+#include "RPCLibrarySourceEmitter.h"
+#include "RPCServerHeaderEmitter.h"
+#include "RPCServerSourceEmitter.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<std::string>
+ 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::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);
+ llvm::sys::path::append(Path, Filename);
+
+ std::error_code EC;
+ auto OutputFile =
+ std::make_unique<llvm::ToolOutputFile>(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<std::string> ClassNames;
+ std::set<std::string> MangledMethodNames;
+ std::set<std::string> SkippedMethodNames;
+ std::set<lldb_rpc_gen::Method> CallbackMethods;
+};
+
+enum SupportLevel {
+ eUnsupported,
+ eUnimplemented,
+ eImplemented,
+};
+
+class SBVisitor : public RecursiveASTVisitor<SBVisitor> {
+public:
+ SBVisitor(
+ GeneratedByproducts &Byproducts, SourceManager &Manager,
+ ASTContext &Context,
+ std::unique_ptr<llvm::ToolOutputFile> &&ServerMethodOutputFile,
+ std::unique_ptr<llvm::ToolOutputFile> &&ServerHeaderOutputFile,
+ std::unique_ptr<llvm::ToolOutputFile> &&LibrarySourceOutputFile,
+ std::unique_ptr<llvm::ToolOutputFile> &&LibraryHeaderOutputFile,
+ lldb_rpc_gen::RPCClientCallbacksSourceEmitter &UserClientSourceEmitter,
+ lldb_rpc_gen::RPCBindingsHarnessEmitter &BindingsHarnessEmitter)
+ : Byproducts(Byproducts), Manager(Manager), Context(Context),
+ ServerSourceEmitter(std::move(ServerMethodOutputFile)),
+ ServerHeaderEmitter(std::move(ServerHeaderOutputFile)),
+ LibrarySourceEmitter(std::move(LibrarySourceOutputFile)),
+ LibraryHeaderEmitter(std::move(LibraryHeaderOutputFile)),
+ ClientCallbacksSourceEmitter(UserClientSourceEmitter),
+ BindingsHarnessEmitter(BindingsHarnessEmitter) {}
+
+ ~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);
+ BindingsHarnessEmitter.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 HasCallbackParameter =
+ lldb_rpc_gen::HasCallbackParameter(MDecl);
+ SupportLevel MethodSupportLevel = GetMethodSupportLevel(MDecl);
+ if (MethodSupportLevel == eImplemented && !IsDisallowed) {
+ const lldb_rpc_gen::Method Method(MDecl, Policy, Context);
+ ServerSourceEmitter.EmitMethod(Method);
+ ServerHeaderEmitter.EmitMethod(Method);
+ LibrarySourceEmitter.EmitMethod(Method);
+ LibraryHeaderEmitter.EmitMethod(Method);
+ BindingsHarnessEmitter.EmitMethod(Method);
+ Byproducts.MangledMethodNames.insert(MangledName);
+ if (HasCallbackParameter) {
+ ClientCallbacksSourceEmitter.EmitMethod(Method);
+ Byproducts.CallbackMethods.insert(Method);
+ }
+ } else if (MethodSupportLevel == eUnimplemented)
+ Byproducts.SkippedMethodNames.insert(MangledName);
+ }
+ LibraryHeaderEmitter.EndClass();
+ LibrarySourceEmitter.EndClass();
+ BindingsHarnessEmitter.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::RPCServerSourceEmitter ServerSourceEmitter;
+ lldb_rpc_gen::RPCServerHeaderEmitter ServerHeaderEmitter;
+ lldb_rpc_gen::RPCLibrarySourceEmitter LibrarySourceEmitter;
+ lldb_rpc_gen::RPCLibraryHeaderEmitter LibraryHeaderEmitter;
+ lldb_rpc_gen::RPCClientCallbacksSourceEmitter &ClientCallbacksSourceEmitter;
+ lldb_rpc_gen::RPCBindingsHarnessEmitter &BindingsHarnessEmitter;
+};
+
+class SBConsumer : public ASTConsumer {
+public:
+ SBConsumer(GeneratedByproducts &Byproducts, SourceManager &Manager,
+ ASTContext &Context,
+ std::unique_ptr<llvm::ToolOutputFile> &&ServerMethodOutputFile,
+ std::unique_ptr<llvm::ToolOutputFile> &&ServerHeaderOutputFile,
+ std::unique_ptr<llvm::ToolOutputFile> &&LibrarySourceOutputFile,
+ std::unique_ptr<llvm::ToolOutputFile> &&LibraryHeaderOutputFile,
+ lldb_rpc_gen::RPCClientCallbacksSourceEmitter
+ &ClientCallbacksSourceEmitter,
+ lldb_rpc_gen::RPCBindingsHarnessEmitter &BindingsHarnessEmitter)
+ : Visitor(Byproducts, Manager, Context, std::move(ServerMethodOutputFile),
+ std::move(ServerHeaderOutputFile),
+ std::move(LibrarySourceOutputFile),
+ std::move(LibraryHeaderOutputFile),
+ ClientCallbacksSourceEmitter, BindingsHarnessEmitter) {}
+ 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,
+ lldb_rpc_gen::RPCBindingsHarnessEmitter &BindingsHarnessEmitter,
+ lldb_rpc_gen::RPCClientCallbacksSourceEmitter &UserClientSourceEmitter)
+ : Byproducts(Byproducts), BindingsHarnessEmitter(BindingsHarnessEmitter),
+ ClientCallbacksSourceEmitter(UserClientSourceEmitter) {}
+
+ std::unique_ptr<ASTConsumer>
+ CreateASTConsumer(CompilerInstance &CI, llvm::StringRef File) override {
+ llvm::StringRef FilenameNoExt =
+ llvm::sys::path::stem(llvm::sys::path::filename(File));
+
+ const std::string ServerMethodFilename =
+ "Server_" + FilenameNoExt.str() + ".cpp";
+ std::unique_ptr<llvm::ToolOutputFile> ServerMethodOutputFile =
+ CreateOutputFile(GetServerOutputDirectory(), ServerMethodFilename);
+ if (!ServerMethodOutputFile)
+ return nullptr;
+
+ const std::string ServerHeaderFilename =
+ "Server_" + FilenameNoExt.str() + ".h";
+ std::unique_ptr<llvm::ToolOutputFile> ServerHeaderOutputFile =
+ CreateOutputFile(GetServerOutputDirectory(), ServerHeaderFilename);
+ 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(LibrarySourceOutputFile), std::move(LibraryHeaderOutputFile),
+ ClientCallbacksSourceEmitter, BindingsHarnessEmitter);
+ }
+
+private:
+ GeneratedByproducts &Byproducts;
+ lldb_rpc_gen::RPCBindingsHarnessEmitter &BindingsHarnessEmitter;
+ lldb_rpc_gen::RPCClientCallbacksSourceEmitter &ClientCallbacksSourceEmitter;
+};
+
+class SBActionFactory : public FrontendActionFactory {
+public:
+ SBActionFactory(
+ GeneratedByproducts &Byproducts,
+ lldb_rpc_gen::RPCBindingsHarnessEmitter &BindingsHarnessEmitter,
+ lldb_rpc_gen::RPCClientCallbacksSourceEmitter
+ &ClientCallbacksSourceEmitter)
+ : Byproducts(Byproducts), BindingsHarnessEmitter(BindingsHarnessEmitter),
+ ClientCallbacksSourceEmitter(ClientCallbacksSourceEmitter) {}
+
+ std::unique_ptr<FrontendAction> create() override {
+ return std::make_unique<SBAction>(Byproducts, BindingsHarnessEmitter,
+ ClientCallbacksSourceEmitter);
+ }
+
+private:
+ GeneratedByproducts &Byproducts;
+ lldb_rpc_gen::RPCBindingsHarnessEmitter &BindingsHarnessEmitter;
+ lldb_rpc_gen::RPCClientCallbacksSourceEmitter &ClientCallbacksSourceEmitter;
+};
+
+bool EmitAmalgamatedServerHeader(const std::vector<std::string> &Files) {
+ // Create the file
+ static constexpr llvm::StringLiteral AmalgamatedServerHeaderName = "SBAPI.h";
+ std::unique_ptr<llvm::ToolOutputFile> AmalgamatedServerHeader =
+ CreateOutputFile(GetServerOutputDirectory(), AmalgamatedServerHeaderName);
+ if (!AmalgamatedServerHeader)
+ return false;
+
+ // Write the header
+ AmalgamatedServerHeader->os()
+ << "#ifndef GENERATED_LLDB_RPC_SERVER_SBAPI_H\n";
+ AmalgamatedServerHeader->os()
+ << "#define GENERATED_LLDB_RPC_SERVER_SBAPI_H\n";
+ for (const auto &File : Files) {
+ llvm::StringRef FilenameNoExt =
+ llvm::sys::path::stem(llvm::sys::path::filename(File));
+ const std::string ServerHeaderFilename =
+ "Server_" + FilenameNoExt.str() + ".h";
+
+ AmalgamatedServerHeader->os()
+ << "#include \"" + ServerHeaderFilename + "\"\n";
+ }
+ AmalgamatedServerHeader->os() << "#include \"SBAPIExtensions.h\"\n";
+ AmalgamatedServerHeader->os()
+ << "#endif // GENERATED_LLDB_RPC_SERVER_SBAPI_H\n";
+ AmalgamatedServerHeader->keep();
+ 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 =
+ 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<std::string> &MangledMethodNames) {
+ static constexpr llvm::StringLiteral MethodNamesFileName = "SBAPI.def";
+ std::unique_ptr<llvm::ToolOutputFile> 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<std::string> &SkippedMethodNames) {
+ static constexpr llvm::StringLiteral FileName = "SkippedMethods.txt";
+ std::unique_ptr<llvm::ToolOutputFile> 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<PCHContainerOperations>();
+ PCHOpts->registerWriter(std::make_unique<ObjectFilePCHContainerWriter>());
+ PCHOpts->registerReader(std::make_unique<ObjectFilePCHContainerReader>());
+
+ ClangTool T(OP.getCompilations(), OP.getSourcePathList(), PCHOpts);
+
+ if (!EmitAmalgamatedServerHeader(OP.getSourcePathList())) {
+ llvm::errs() << "Failed to create amalgamated server header\n";
+ return 1;
+ }
+
+ if (!EmitAmalgamatedLibraryHeader(OP.getSourcePathList())) {
+ llvm::errs() << "Failed to create amalgamated library header\n";
+ return 1;
+ }
+
+ GeneratedByproducts Byproducts;
+
+ constexpr llvm::StringLiteral BindingsHarnessFilename = "lldb.py";
+ std::unique_ptr<llvm::ToolOutputFile> BindingsHarnessOutputFile =
+ CreateOutputFile(OutputDir.getValue(), BindingsHarnessFilename);
+
+ if (!BindingsHarnessOutputFile) {
+ llvm::errs() << "Failed to create the bindings harness file\n";
+ return 1;
+ }
+ BindingsHarnessOutputFile->keep();
+ lldb_rpc_gen::RPCBindingsHarnessEmitter BindingsHarnessEmitter(
+ std::move(BindingsHarnessOutputFile));
+
+ static constexpr llvm::StringLiteral FileName = "RPCClientSideCallbacks.cpp";
+ std::unique_ptr<llvm::ToolOutputFile> &&UserClientSourceOutputFile =
+ CreateOutputFile(GetLibraryOutputDirectory(), FileName);
+ if (!UserClientSourceOutputFile) {
+ llvm::errs() << "Failed to create the user client callbacks source file\n";
+ return 1;
+ }
+
+ UserClientSourceOutputFile->keep();
+ lldb_rpc_gen::RPCClientCallbacksSourceEmitter ClientCallbacksSourceEmitter(
+ std::move(UserClientSourceOutputFile));
+
+ SBActionFactory Factory(Byproducts, BindingsHarnessEmitter,
+ ClientCallbacksSourceEmitter);
+ auto Result = T.run(&Factory);
+ ClientCallbacksSourceEmitter.EmitRPCClientInitialize(
+ Byproducts.CallbackMethods);
+ 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;
+}
More information about the lldb-commits
mailing list