forked from OSchip/llvm-project
218 lines
6.7 KiB
C++
218 lines
6.7 KiB
C++
//===--- XPCTransport.cpp - sending and receiving LSP messages over XPC ---===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
#include "Conversion.h"
|
|
#include "Protocol.h" // For LSPError
|
|
#include "Transport.h"
|
|
#include "support/Logger.h"
|
|
#include "llvm/Support/Errno.h"
|
|
|
|
#include <xpc/xpc.h>
|
|
|
|
using namespace llvm;
|
|
using namespace clang;
|
|
using namespace clangd;
|
|
|
|
namespace {
|
|
|
|
json::Object encodeError(Error E) {
|
|
std::string Message;
|
|
ErrorCode Code = ErrorCode::UnknownErrorCode;
|
|
if (Error Unhandled =
|
|
handleErrors(std::move(E), [&](const LSPError &L) -> Error {
|
|
Message = L.Message;
|
|
Code = L.Code;
|
|
return Error::success();
|
|
}))
|
|
Message = toString(std::move(Unhandled));
|
|
|
|
return json::Object{
|
|
{"message", std::move(Message)},
|
|
{"code", int64_t(Code)},
|
|
};
|
|
}
|
|
|
|
Error decodeError(const json::Object &O) {
|
|
std::string Msg =
|
|
std::string(O.getString("message").getValueOr("Unspecified error"));
|
|
if (auto Code = O.getInteger("code"))
|
|
return make_error<LSPError>(std::move(Msg), ErrorCode(*Code));
|
|
return error("{0}", Msg);
|
|
}
|
|
|
|
// C "closure" for XPCTransport::loop() method
|
|
namespace xpcClosure {
|
|
void connection_handler(xpc_connection_t clientConnection);
|
|
} // namespace xpcClosure
|
|
|
|
class XPCTransport : public Transport {
|
|
public:
|
|
XPCTransport() {}
|
|
|
|
void notify(StringRef Method, json::Value Params) override {
|
|
sendMessage(json::Object{
|
|
{"jsonrpc", "2.0"},
|
|
{"method", Method},
|
|
{"params", std::move(Params)},
|
|
});
|
|
}
|
|
void call(StringRef Method, json::Value Params, json::Value ID) override {
|
|
sendMessage(json::Object{
|
|
{"jsonrpc", "2.0"},
|
|
{"id", std::move(ID)},
|
|
{"method", Method},
|
|
{"params", std::move(Params)},
|
|
});
|
|
}
|
|
void reply(json::Value ID, Expected<json::Value> Result) override {
|
|
if (Result) {
|
|
sendMessage(json::Object{
|
|
{"jsonrpc", "2.0"},
|
|
{"id", std::move(ID)},
|
|
{"result", std::move(*Result)},
|
|
});
|
|
} else {
|
|
sendMessage(json::Object{
|
|
{"jsonrpc", "2.0"},
|
|
{"id", std::move(ID)},
|
|
{"error", encodeError(Result.takeError())},
|
|
});
|
|
}
|
|
}
|
|
|
|
Error loop(MessageHandler &Handler) override;
|
|
|
|
private:
|
|
// Needs access to handleMessage() and resetClientConnection()
|
|
friend void xpcClosure::connection_handler(xpc_connection_t clientConnection);
|
|
|
|
// Dispatches incoming message to Handler onNotify/onCall/onReply.
|
|
bool handleMessage(json::Value Message, MessageHandler &Handler);
|
|
void sendMessage(json::Value Message) {
|
|
xpc_object_t response = jsonToXpc(Message);
|
|
xpc_connection_send_message(clientConnection, response);
|
|
xpc_release(response);
|
|
}
|
|
void resetClientConnection(xpc_connection_t newClientConnection) {
|
|
clientConnection = newClientConnection;
|
|
}
|
|
xpc_connection_t clientConnection;
|
|
};
|
|
|
|
bool XPCTransport::handleMessage(json::Value Message, MessageHandler &Handler) {
|
|
// Message must be an object with "jsonrpc":"2.0".
|
|
auto *Object = Message.getAsObject();
|
|
if (!Object || Object->getString("jsonrpc") != Optional<StringRef>("2.0")) {
|
|
elog("Not a JSON-RPC 2.0 message: {0:2}", Message);
|
|
return false;
|
|
}
|
|
// ID may be any JSON value. If absent, this is a notification.
|
|
Optional<json::Value> ID;
|
|
if (auto *I = Object->get("id"))
|
|
ID = std::move(*I);
|
|
auto Method = Object->getString("method");
|
|
if (!Method) { // This is a response.
|
|
if (!ID) {
|
|
elog("No method and no response ID: {0:2}", Message);
|
|
return false;
|
|
}
|
|
if (auto *Err = Object->getObject("error"))
|
|
return Handler.onReply(std::move(*ID), decodeError(*Err));
|
|
// Result should be given, use null if not.
|
|
json::Value Result = nullptr;
|
|
if (auto *R = Object->get("result"))
|
|
Result = std::move(*R);
|
|
return Handler.onReply(std::move(*ID), std::move(Result));
|
|
}
|
|
// Params should be given, use null if not.
|
|
json::Value Params = nullptr;
|
|
if (auto *P = Object->get("params"))
|
|
Params = std::move(*P);
|
|
|
|
if (ID)
|
|
return Handler.onCall(*Method, std::move(Params), std::move(*ID));
|
|
else
|
|
return Handler.onNotify(*Method, std::move(Params));
|
|
}
|
|
|
|
namespace xpcClosure {
|
|
// "owner" of this "closure object" - necessary for propagating connection to
|
|
// XPCTransport so it can send messages to the client.
|
|
XPCTransport *TransportObject = nullptr;
|
|
Transport::MessageHandler *HandlerPtr = nullptr;
|
|
|
|
void connection_handler(xpc_connection_t clientConnection) {
|
|
xpc_connection_set_target_queue(clientConnection, dispatch_get_main_queue());
|
|
|
|
xpc_transaction_begin();
|
|
|
|
TransportObject->resetClientConnection(clientConnection);
|
|
|
|
xpc_connection_set_event_handler(clientConnection, ^(xpc_object_t message) {
|
|
if (message == XPC_ERROR_CONNECTION_INVALID) {
|
|
// connection is being terminated
|
|
log("Received XPC_ERROR_CONNECTION_INVALID message - returning from the "
|
|
"event_handler.");
|
|
return;
|
|
}
|
|
|
|
if (xpc_get_type(message) != XPC_TYPE_DICTIONARY) {
|
|
log("Received XPC message of unknown type - returning from the "
|
|
"event_handler.");
|
|
return;
|
|
}
|
|
|
|
const json::Value Doc = xpcToJson(message);
|
|
if (Doc == json::Value(nullptr)) {
|
|
log("XPC message was converted to Null JSON message - returning from the "
|
|
"event_handler.");
|
|
return;
|
|
}
|
|
|
|
vlog("<<< {0}\n", Doc);
|
|
|
|
if (!TransportObject->handleMessage(std::move(Doc), *HandlerPtr)) {
|
|
log("Received exit notification - cancelling connection.");
|
|
xpc_connection_cancel(xpc_dictionary_get_remote_connection(message));
|
|
xpc_transaction_end();
|
|
}
|
|
});
|
|
|
|
xpc_connection_resume(clientConnection);
|
|
}
|
|
} // namespace xpcClosure
|
|
|
|
Error XPCTransport::loop(MessageHandler &Handler) {
|
|
assert(xpcClosure::TransportObject == nullptr &&
|
|
"TransportObject has already been set.");
|
|
// This looks scary since lifetime of this (or any) XPCTransport object has
|
|
// to fully contain lifetime of any XPC connection. In practise any Transport
|
|
// object is destroyed only at the end of main() which is always after
|
|
// exit of xpc_main().
|
|
xpcClosure::TransportObject = this;
|
|
|
|
assert(xpcClosure::HandlerPtr == nullptr &&
|
|
"HandlerPtr has already been set.");
|
|
xpcClosure::HandlerPtr = &Handler;
|
|
|
|
xpc_main(xpcClosure::connection_handler);
|
|
// xpc_main doesn't ever return
|
|
return errorCodeToError(std::make_error_code(std::errc::io_error));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
|
|
std::unique_ptr<Transport> newXPCTransport() {
|
|
return std::make_unique<XPCTransport>();
|
|
}
|
|
|
|
} // namespace clangd
|
|
} // namespace clang
|