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