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