diff options
Diffstat (limited to 'clangd/xpc/XPCTransport.cpp')
-rw-r--r-- | clangd/xpc/XPCTransport.cpp | 217 |
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 |