summaryrefslogtreecommitdiffstats
path: root/clangd/xpc/XPCTransport.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'clangd/xpc/XPCTransport.cpp')
-rw-r--r--clangd/xpc/XPCTransport.cpp217
1 files changed, 217 insertions, 0 deletions
diff --git a/clangd/xpc/XPCTransport.cpp b/clangd/xpc/XPCTransport.cpp
new file mode 100644
index 00000000..496d8e47
--- /dev/null
+++ b/clangd/xpc/XPCTransport.cpp
@@ -0,0 +1,217 @@
+//===--- XPCTransport.cpp - sending and receiving LSP messages over XPC ---===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+#include "Conversion.h"
+#include "Logger.h"
+#include "Protocol.h" // For LSPError
+#include "Transport.h"
+#include "llvm/Support/Errno.h"
+
+#include <xpc/xpc.h>
+
+using namespace llvm;
+using namespace clang;
+using namespace clangd;
+
+namespace {
+
+json::Object encodeError(Error E) {
+ std::string Message;
+ ErrorCode Code = ErrorCode::UnknownErrorCode;
+ if (Error Unhandled =
+ handleErrors(std::move(E), [&](const LSPError &L) -> Error {
+ Message = L.Message;
+ Code = L.Code;
+ return Error::success();
+ }))
+ Message = toString(std::move(Unhandled));
+
+ return json::Object{
+ {"message", std::move(Message)},
+ {"code", int64_t(Code)},
+ };
+}
+
+Error decodeError(const json::Object &O) {
+ std::string Msg = O.getString("message").getValueOr("Unspecified error");
+ if (auto Code = O.getInteger("code"))
+ return make_error<LSPError>(std::move(Msg), ErrorCode(*Code));
+ return make_error<StringError>(std::move(Msg), inconvertibleErrorCode());
+}
+
+// C "closure" for XPCTransport::loop() method
+namespace xpcClosure {
+void connection_handler(xpc_connection_t clientConnection);
+}
+
+class XPCTransport : public Transport {
+public:
+ XPCTransport() {}
+
+ void notify(StringRef Method, json::Value Params) override {
+ sendMessage(json::Object{
+ {"jsonrpc", "2.0"},
+ {"method", Method},
+ {"params", std::move(Params)},
+ });
+ }
+ void call(StringRef Method, json::Value Params, json::Value ID) override {
+ sendMessage(json::Object{
+ {"jsonrpc", "2.0"},
+ {"id", std::move(ID)},
+ {"method", Method},
+ {"params", std::move(Params)},
+ });
+ }
+ void reply(json::Value ID, Expected<json::Value> Result) override {
+ if (Result) {
+ sendMessage(json::Object{
+ {"jsonrpc", "2.0"},
+ {"id", std::move(ID)},
+ {"result", std::move(*Result)},
+ });
+ } else {
+ sendMessage(json::Object{
+ {"jsonrpc", "2.0"},
+ {"id", std::move(ID)},
+ {"error", encodeError(Result.takeError())},
+ });
+ }
+ }
+
+ Error loop(MessageHandler &Handler) override;
+
+private:
+ // Needs access to handleMessage() and resetClientConnection()
+ friend void xpcClosure::connection_handler(xpc_connection_t clientConnection);
+
+ // Dispatches incoming message to Handler onNotify/onCall/onReply.
+ bool handleMessage(json::Value Message, MessageHandler &Handler);
+ void sendMessage(json::Value Message) {
+ xpc_object_t response = jsonToXpc(Message);
+ xpc_connection_send_message(clientConnection, response);
+ xpc_release(response);
+ }
+ void resetClientConnection(xpc_connection_t newClientConnection) {
+ clientConnection = newClientConnection;
+ }
+ xpc_connection_t clientConnection;
+};
+
+bool XPCTransport::handleMessage(json::Value Message, MessageHandler &Handler) {
+ // Message must be an object with "jsonrpc":"2.0".
+ auto *Object = Message.getAsObject();
+ if (!Object || Object->getString("jsonrpc") != Optional<StringRef>("2.0")) {
+ elog("Not a JSON-RPC 2.0 message: {0:2}", Message);
+ return false;
+ }
+ // ID may be any JSON value. If absent, this is a notification.
+ Optional<json::Value> ID;
+ if (auto *I = Object->get("id"))
+ ID = std::move(*I);
+ auto Method = Object->getString("method");
+ if (!Method) { // This is a response.
+ if (!ID) {
+ elog("No method and no response ID: {0:2}", Message);
+ return false;
+ }
+ if (auto *Err = Object->getObject("error"))
+ return Handler.onReply(std::move(*ID), decodeError(*Err));
+ // Result should be given, use null if not.
+ json::Value Result = nullptr;
+ if (auto *R = Object->get("result"))
+ Result = std::move(*R);
+ return Handler.onReply(std::move(*ID), std::move(Result));
+ }
+ // Params should be given, use null if not.
+ json::Value Params = nullptr;
+ if (auto *P = Object->get("params"))
+ Params = std::move(*P);
+
+ if (ID)
+ return Handler.onCall(*Method, std::move(Params), std::move(*ID));
+ else
+ return Handler.onNotify(*Method, std::move(Params));
+}
+
+namespace xpcClosure {
+// "owner" of this "closure object" - necessary for propagating connection to
+// XPCTransport so it can send messages to the client.
+XPCTransport *TransportObject = nullptr;
+Transport::MessageHandler *HandlerPtr = nullptr;
+
+void connection_handler(xpc_connection_t clientConnection) {
+ xpc_connection_set_target_queue(clientConnection, dispatch_get_main_queue());
+
+ xpc_transaction_begin();
+
+ TransportObject->resetClientConnection(clientConnection);
+
+ xpc_connection_set_event_handler(clientConnection, ^(xpc_object_t message) {
+ if (message == XPC_ERROR_CONNECTION_INVALID) {
+ // connection is being terminated
+ log("Received XPC_ERROR_CONNECTION_INVALID message - returning from the "
+ "event_handler.");
+ return;
+ }
+
+ if (xpc_get_type(message) != XPC_TYPE_DICTIONARY) {
+ log("Received XPC message of unknown type - returning from the "
+ "event_handler.");
+ return;
+ }
+
+ const json::Value Doc = xpcToJson(message);
+ if (Doc == json::Value(nullptr)) {
+ log("XPC message was converted to Null JSON message - returning from the "
+ "event_handler.");
+ return;
+ }
+
+ vlog("<<< {0}\n", Doc);
+
+ if (!TransportObject->handleMessage(std::move(Doc), *HandlerPtr)) {
+ log("Received exit notification - cancelling connection.");
+ xpc_connection_cancel(xpc_dictionary_get_remote_connection(message));
+ xpc_transaction_end();
+ }
+ });
+
+ xpc_connection_resume(clientConnection);
+}
+} // namespace xpcClosure
+
+Error XPCTransport::loop(MessageHandler &Handler) {
+ assert(xpcClosure::TransportObject == nullptr &&
+ "TransportObject has already been set.");
+ // This looks scary since lifetime of this (or any) XPCTransport object has
+ // to fully contain lifetime of any XPC connection. In practise any Transport
+ // object is destroyed only at the end of main() which is always after
+ // exit of xpc_main().
+ xpcClosure::TransportObject = this;
+
+ assert(xpcClosure::HandlerPtr == nullptr &&
+ "HandlerPtr has already been set.");
+ xpcClosure::HandlerPtr = &Handler;
+
+ xpc_main(xpcClosure::connection_handler);
+ // xpc_main doesn't ever return
+ return errorCodeToError(std::make_error_code(std::errc::io_error));
+}
+
+} // namespace
+
+namespace clang {
+namespace clangd {
+
+std::unique_ptr<Transport> newXPCTransport() {
+ return llvm::make_unique<XPCTransport>();
+}
+
+} // namespace clangd
+} // namespace clang