xref: /llvm-project/clang-tools-extra/clangd/xpc/XPCTransport.cpp (revision f71ffd3b735b4d6ae3c12be1806cdd6205b3b378)
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 
17 using namespace llvm;
18 using namespace clang;
19 using namespace clangd;
20 
21 namespace {
22 
encodeError(Error E)23 json::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 
decodeError(const json::Object & O)40 Error 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("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
49 namespace xpcClosure {
50 void connection_handler(xpc_connection_t clientConnection);
51 } // namespace xpcClosure
52 
53 class XPCTransport : public Transport {
54 public:
XPCTransport()55   XPCTransport() {}
56 
notify(StringRef Method,json::Value Params)57   void notify(StringRef Method, json::Value Params) override {
58     sendMessage(json::Object{
59         {"jsonrpc", "2.0"},
60         {"method", Method},
61         {"params", std::move(Params)},
62     });
63   }
call(StringRef Method,json::Value Params,json::Value ID)64   void call(StringRef Method, json::Value Params, json::Value ID) override {
65     sendMessage(json::Object{
66         {"jsonrpc", "2.0"},
67         {"id", std::move(ID)},
68         {"method", Method},
69         {"params", std::move(Params)},
70     });
71   }
reply(json::Value ID,Expected<json::Value> Result)72   void reply(json::Value ID, Expected<json::Value> Result) override {
73     if (Result) {
74       sendMessage(json::Object{
75           {"jsonrpc", "2.0"},
76           {"id", std::move(ID)},
77           {"result", std::move(*Result)},
78       });
79     } else {
80       sendMessage(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 
90 private:
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);
sendMessage(json::Value Message)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   }
resetClientConnection(xpc_connection_t newClientConnection)101   void resetClientConnection(xpc_connection_t newClientConnection) {
102     clientConnection = newClientConnection;
103   }
104   xpc_connection_t clientConnection;
105 };
106 
handleMessage(json::Value Message,MessageHandler & Handler)107 bool 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("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("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));
132   }
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);
137 
138   if (ID)
139     return Handler.onCall(*Method, std::move(Params), std::move(*ID));
140   else
141     return Handler.onNotify(*Method, std::move(Params));
142 }
143 
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;
149 
connection_handler(xpc_connection_t clientConnection)150 void 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(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();
184     }
185   });
186 
187   xpc_connection_resume(clientConnection);
188 }
189 } // namespace xpcClosure
190 
loop(MessageHandler & Handler)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;
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(std::make_error_code(std::errc::io_error));
207 }
208 
209 } // namespace
210 
211 namespace clang {
212 namespace clangd {
213 
newXPCTransport()214 std::unique_ptr<Transport> newXPCTransport() {
215   return std::make_unique<XPCTransport>();
216 }
217 
218 } // namespace clangd
219 } // namespace clang
220