1//===--- XPCTransport.cpp - sending and receiving LSP messages over XPC ---===//
2//
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
6//
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"
13
14#include <optional>
15#include <xpc/xpc.h>
16
17using namespace llvm;
18using namespace clang;
19using namespace clangd;
20
21namespace {
22
23json::Object encodeError(Error E) {
24 std::string Message;
25 ErrorCode Code = ErrorCode::UnknownErrorCode;
26 if (Error Unhandled =
27 handleErrors(std::move(E), [&](const LSPError &L) -> Error {
28 Message = L.Message;
29 Code = L.Code;
30 return Error::success();
31 }))
32 Message = toString(std::move(Unhandled));
33
34 return json::Object{
35 {"message", std::move(Message)},
36 {"code", int64_t(Code)},
37 };
38}
39
40Error decodeError(const json::Object &O) {
41 std::string Msg =
42 std::string(O.getString("message").value_or("Unspecified error"));
43 if (auto Code = O.getInteger(K: "code"))
44 return make_error<LSPError>(std::move(Msg), ErrorCode(*Code));
45 return error("{0}", Msg);
46}
47
48// C "closure" for XPCTransport::loop() method
49namespace xpcClosure {
50void connection_handler(xpc_connection_t clientConnection);
51} // namespace xpcClosure
52
53class XPCTransport : public Transport {
54public:
55 XPCTransport() {}
56
57 void notify(StringRef Method, json::Value Params) override {
58 sendMessage(Message: json::Object{
59 {"jsonrpc", "2.0"},
60 {"method", Method},
61 {"params", std::move(Params)},
62 });
63 }
64 void call(StringRef Method, json::Value Params, json::Value ID) override {
65 sendMessage(Message: json::Object{
66 {"jsonrpc", "2.0"},
67 {"id", std::move(ID)},
68 {"method", Method},
69 {"params", std::move(Params)},
70 });
71 }
72 void reply(json::Value ID, Expected<json::Value> Result) override {
73 if (Result) {
74 sendMessage(Message: json::Object{
75 {"jsonrpc", "2.0"},
76 {"id", std::move(ID)},
77 {"result", std::move(*Result)},
78 });
79 } else {
80 sendMessage(Message: json::Object{
81 {"jsonrpc", "2.0"},
82 {"id", std::move(ID)},
83 {"error", encodeError(Result.takeError())},
84 });
85 }
86 }
87
88 Error loop(MessageHandler &Handler) override;
89
90private:
91 // Needs access to handleMessage() and resetClientConnection()
92 friend void xpcClosure::connection_handler(xpc_connection_t clientConnection);
93
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);
100 }
101 void resetClientConnection(xpc_connection_t newClientConnection) {
102 clientConnection = newClientConnection;
103 }
104 xpc_connection_t clientConnection;
105};
106
107bool XPCTransport::handleMessage(json::Value Message, MessageHandler &Handler) {
108 // Message must be an object with "jsonrpc":"2.0".
109 auto *Object = Message.getAsObject();
110 if (!Object ||
111 Object->getString("jsonrpc") != std::optional<StringRef>("2.0")) {
112 elog("Not a JSON-RPC 2.0 message: {0:2}", Message);
113 return false;
114 }
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(K: "id"))
118 ID = std::move(*I);
119 auto Method = Object->getString("method");
120 if (!Method) { // This is a response.
121 if (!ID) {
122 elog("No method and no response ID: {0:2}", Message);
123 return false;
124 }
125 if (auto *Err = Object->getObject(K: "error"))
126 return Handler.onReply(ID: std::move(*ID), Result: decodeError(O: *Err));
127 // Result should be given, use null if not.
128 json::Value Result = nullptr;
129 if (auto *R = Object->get(K: "result"))
130 Result = std::move(*R);
131 return Handler.onReply(ID: std::move(*ID), Result: std::move(Result));
132 }
133 // Params should be given, use null if not.
134 json::Value Params = nullptr;
135 if (auto *P = Object->get(K: "params"))
136 Params = std::move(*P);
137
138 if (ID)
139 return Handler.onCall(Method: *Method, Params: std::move(Params), ID: std::move(*ID));
140 else
141 return Handler.onNotify(Method: *Method, std::move(Params));
142}
143
144namespace xpcClosure {
145// "owner" of this "closure object" - necessary for propagating connection to
146// XPCTransport so it can send messages to the client.
147XPCTransport *TransportObject = nullptr;
148Transport::MessageHandler *HandlerPtr = nullptr;
149
150void connection_handler(xpc_connection_t clientConnection) {
151 xpc_connection_set_target_queue(clientConnection, dispatch_get_main_queue());
152
153 xpc_transaction_begin();
154
155 TransportObject->resetClientConnection(clientConnection);
156
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 "
161 "event_handler.");
162 return;
163 }
164
165 if (xpc_get_type(message) != XPC_TYPE_DICTIONARY) {
166 log("Received XPC message of unknown type - returning from the "
167 "event_handler.");
168 return;
169 }
170
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 "
174 "event_handler.");
175 return;
176 }
177
178 vlog("<<< {0}\n", Doc);
179
180 if (!TransportObject->handleMessage(Message: std::move(Doc), Handler&: *HandlerPtr)) {
181 log("Received exit notification - cancelling connection.");
182 xpc_connection_cancel(xpc_dictionary_get_remote_connection(message));
183 xpc_transaction_end();
184 }
185 });
186
187 xpc_connection_resume(clientConnection);
188}
189} // namespace xpcClosure
190
191Error 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;
199
200 assert(xpcClosure::HandlerPtr == nullptr &&
201 "HandlerPtr has already been set.");
202 xpcClosure::HandlerPtr = &Handler;
203
204 xpc_main(xpcClosure::connection_handler);
205 // xpc_main doesn't ever return
206 return errorCodeToError(EC: std::make_error_code(e: std::errc::io_error));
207}
208
209} // namespace
210
211namespace clang {
212namespace clangd {
213
214std::unique_ptr<Transport> newXPCTransport() {
215 return std::make_unique<XPCTransport>();
216}
217
218} // namespace clangd
219} // namespace clang
220

source code of clang-tools-extra/clangd/xpc/XPCTransport.cpp