1 //===--- XPCTransport.cpp - sending and receiving LSP messages over XPC ---===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
8 #include "Conversion.h"
9 #include "Protocol.h" // For LSPError
10 #include "Transport.h"
11 #include "support/Logger.h"
12 #include "llvm/Support/Errno.h"
18 using namespace clang
;
19 using namespace clangd
;
23 json::Object
encodeError(Error E
) {
25 ErrorCode Code
= ErrorCode::UnknownErrorCode
;
27 handleErrors(std::move(E
), [&](const LSPError
&L
) -> Error
{
30 return Error::success();
32 Message
= toString(std::move(Unhandled
));
35 {"message", std::move(Message
)},
36 {"code", int64_t(Code
)},
40 Error
decodeError(const json::Object
&O
) {
42 std::string(O
.getString("message").value_or("Unspecified error"));
43 if (auto Code
= O
.getInteger("code"))
44 return make_error
<LSPError
>(std::move(Msg
), ErrorCode(*Code
));
45 return error("{0}", Msg
);
48 // C "closure" for XPCTransport::loop() method
49 namespace xpcClosure
{
50 void connection_handler(xpc_connection_t clientConnection
);
51 } // namespace xpcClosure
53 class XPCTransport
: public Transport
{
57 void notify(StringRef Method
, json::Value Params
) override
{
58 sendMessage(json::Object
{
61 {"params", std::move(Params
)},
64 void call(StringRef Method
, json::Value Params
, json::Value ID
) override
{
65 sendMessage(json::Object
{
67 {"id", std::move(ID
)},
69 {"params", std::move(Params
)},
72 void reply(json::Value ID
, Expected
<json::Value
> Result
) override
{
74 sendMessage(json::Object
{
76 {"id", std::move(ID
)},
77 {"result", std::move(*Result
)},
80 sendMessage(json::Object
{
82 {"id", std::move(ID
)},
83 {"error", encodeError(Result
.takeError())},
88 Error
loop(MessageHandler
&Handler
) override
;
91 // Needs access to handleMessage() and resetClientConnection()
92 friend void xpcClosure::connection_handler(xpc_connection_t clientConnection
);
94 // Dispatches incoming message to Handler onNotify/onCall/onReply.
95 bool handleMessage(json::Value Message
, MessageHandler
&Handler
);
96 void sendMessage(json::Value Message
) {
97 xpc_object_t response
= jsonToXpc(Message
);
98 xpc_connection_send_message(clientConnection
, response
);
99 xpc_release(response
);
101 void resetClientConnection(xpc_connection_t newClientConnection
) {
102 clientConnection
= newClientConnection
;
104 xpc_connection_t clientConnection
;
107 bool XPCTransport::handleMessage(json::Value Message
, MessageHandler
&Handler
) {
108 // Message must be an object with "jsonrpc":"2.0".
109 auto *Object
= Message
.getAsObject();
111 Object
->getString("jsonrpc") != std::optional
<StringRef
>("2.0")) {
112 elog("Not a JSON-RPC 2.0 message: {0:2}", Message
);
115 // ID may be any JSON value. If absent, this is a notification.
116 std::optional
<json::Value
> ID
;
117 if (auto *I
= Object
->get("id"))
119 auto Method
= Object
->getString("method");
120 if (!Method
) { // This is a response.
122 elog("No method and no response ID: {0:2}", Message
);
125 if (auto *Err
= Object
->getObject("error"))
126 return Handler
.onReply(std::move(*ID
), decodeError(*Err
));
127 // Result should be given, use null if not.
128 json::Value Result
= nullptr;
129 if (auto *R
= Object
->get("result"))
130 Result
= std::move(*R
);
131 return Handler
.onReply(std::move(*ID
), std::move(Result
));
133 // Params should be given, use null if not.
134 json::Value Params
= nullptr;
135 if (auto *P
= Object
->get("params"))
136 Params
= std::move(*P
);
139 return Handler
.onCall(*Method
, std::move(Params
), std::move(*ID
));
141 return Handler
.onNotify(*Method
, std::move(Params
));
144 namespace xpcClosure
{
145 // "owner" of this "closure object" - necessary for propagating connection to
146 // XPCTransport so it can send messages to the client.
147 XPCTransport
*TransportObject
= nullptr;
148 Transport::MessageHandler
*HandlerPtr
= nullptr;
150 void connection_handler(xpc_connection_t clientConnection
) {
151 xpc_connection_set_target_queue(clientConnection
, dispatch_get_main_queue());
153 xpc_transaction_begin();
155 TransportObject
->resetClientConnection(clientConnection
);
157 xpc_connection_set_event_handler(clientConnection
, ^(xpc_object_t message
) {
158 if (message
== XPC_ERROR_CONNECTION_INVALID
) {
159 // connection is being terminated
160 log("Received XPC_ERROR_CONNECTION_INVALID message - returning from the "
165 if (xpc_get_type(message
) != XPC_TYPE_DICTIONARY
) {
166 log("Received XPC message of unknown type - returning from the "
171 const json::Value Doc
= xpcToJson(message
);
172 if (Doc
== json::Value(nullptr)) {
173 log("XPC message was converted to Null JSON message - returning from the "
178 vlog("<<< {0}\n", Doc
);
180 if (!TransportObject
->handleMessage(std::move(Doc
), *HandlerPtr
)) {
181 log("Received exit notification - cancelling connection.");
182 xpc_connection_cancel(xpc_dictionary_get_remote_connection(message
));
183 xpc_transaction_end();
187 xpc_connection_resume(clientConnection
);
189 } // namespace xpcClosure
191 Error
XPCTransport::loop(MessageHandler
&Handler
) {
192 assert(xpcClosure::TransportObject
== nullptr &&
193 "TransportObject has already been set.");
194 // This looks scary since lifetime of this (or any) XPCTransport object has
195 // to fully contain lifetime of any XPC connection. In practise any Transport
196 // object is destroyed only at the end of main() which is always after
197 // exit of xpc_main().
198 xpcClosure::TransportObject
= this;
200 assert(xpcClosure::HandlerPtr
== nullptr &&
201 "HandlerPtr has already been set.");
202 xpcClosure::HandlerPtr
= &Handler
;
204 xpc_main(xpcClosure::connection_handler
);
205 // xpc_main doesn't ever return
206 return errorCodeToError(std::make_error_code(std::errc::io_error
));
214 std::unique_ptr
<Transport
> newXPCTransport() {
215 return std::make_unique
<XPCTransport
>();
218 } // namespace clangd