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 make_error<StringError>(std::move(Msg), inconvertibleErrorCode());
 | |
| }
 | |
| 
 | |
| // C "closure" for XPCTransport::loop() method
 | |
| namespace xpcClosure {
 | |
| void connection_handler(xpc_connection_t clientConnection);
 | |
| }
 | |
| 
 | |
| 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
 |