mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
This allows us to load plugins into the sourcekitd service to handle requests (currently only used for code completion). This allows us to implement parts of sourcekitd in Swift and outside of the compiler repository, making it easier to iterated on them because the compiler doesn’t need to be rebuilt.
520 lines
17 KiB
C++
520 lines
17 KiB
C++
//===--- sourcekitd.cpp ---------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "sourcekitd/Internal-XPC.h"
|
|
#include "SourceKit/Support/Logging.h"
|
|
#include "SourceKit/Support/UIdent.h"
|
|
|
|
#include "llvm/Support/ErrorHandling.h"
|
|
#include "llvm/Support/Mutex.h"
|
|
#include <chrono>
|
|
#include <xpc/xpc.h>
|
|
#include <dispatch/dispatch.h>
|
|
|
|
#include <Block.h>
|
|
|
|
using namespace SourceKit;
|
|
using namespace sourcekitd;
|
|
|
|
static UIdent gKeyNotification("key.notification");
|
|
static UIdent gKeyDuration("key.duration");
|
|
static UIdent gSemaDisableNotificationUID("source.notification.sema_disabled");
|
|
|
|
static llvm::sys::Mutex GlobalHandlersMtx;
|
|
static sourcekitd_uid_handler_t UidMappingHandler;
|
|
static sourcekitd_str_from_uid_handler_t StrFromUidMappingHandler;
|
|
|
|
void
|
|
sourcekitd_set_uid_handler(sourcekitd_uid_handler_t handler) {
|
|
llvm::sys::ScopedLock L(GlobalHandlersMtx);
|
|
sourcekitd_uid_handler_t newHandler = Block_copy(handler);
|
|
Block_release(UidMappingHandler);
|
|
UidMappingHandler = newHandler;
|
|
}
|
|
|
|
void
|
|
sourcekitd_set_uid_handlers(sourcekitd_uid_from_str_handler_t uid_from_str,
|
|
sourcekitd_str_from_uid_handler_t str_from_uid) {
|
|
llvm::sys::ScopedLock L(GlobalHandlersMtx);
|
|
|
|
sourcekitd_uid_handler_t newUIDFromStrHandler = Block_copy(uid_from_str);
|
|
Block_release(UidMappingHandler);
|
|
UidMappingHandler = newUIDFromStrHandler;
|
|
|
|
sourcekitd_str_from_uid_handler_t newStrFromUIDHandler = Block_copy(str_from_uid);
|
|
Block_release(StrFromUidMappingHandler);
|
|
StrFromUidMappingHandler = newStrFromUIDHandler;
|
|
}
|
|
|
|
sourcekitd_uid_t sourcekitd::SKDUIDFromUIdent(UIdent UID) {
|
|
if (void *Tag = UID.getTag())
|
|
return reinterpret_cast<sourcekitd_uid_t>(Tag);
|
|
|
|
if (UidMappingHandler) {
|
|
sourcekitd_uid_t skduid = UidMappingHandler(UID.c_str());
|
|
if (skduid) {
|
|
UID.setTag(skduid);
|
|
return skduid;
|
|
}
|
|
}
|
|
|
|
return reinterpret_cast<sourcekitd_uid_t>(UID.getAsOpaqueValue());
|
|
}
|
|
|
|
UIdent sourcekitd::UIdentFromSKDUID(sourcekitd_uid_t uid) {
|
|
if (StrFromUidMappingHandler) {
|
|
if (const char *str = StrFromUidMappingHandler(uid))
|
|
return UIdent(str);
|
|
}
|
|
|
|
return UIdent::getFromOpaqueValue(uid);
|
|
}
|
|
|
|
static xpc_connection_t GlobalConn = nullptr;
|
|
static sourcekitd_interrupted_connection_handler_t InterruptedConnectionHandler;
|
|
static sourcekitd_response_receiver_t NotificationReceiver;
|
|
|
|
void
|
|
sourcekitd_set_interrupted_connection_handler(
|
|
sourcekitd_interrupted_connection_handler_t handler) {
|
|
sourcekitd_interrupted_connection_handler_t newHandler = Block_copy(handler);
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
Block_release(InterruptedConnectionHandler);
|
|
InterruptedConnectionHandler = newHandler;
|
|
});
|
|
}
|
|
|
|
void
|
|
sourcekitd_set_notification_handler(sourcekitd_response_receiver_t receiver) {
|
|
sourcekitd_response_receiver_t newReceiver = Block_copy(receiver);
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
Block_release(NotificationReceiver);
|
|
NotificationReceiver = newReceiver;
|
|
});
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// sourcekitd_request_sync
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
/// Create a new SourceKit request handle. Each call of this method is
|
|
/// guaranteed to return a new, unique handle.
|
|
static sourcekitd_request_handle_t create_request_handle(void) {
|
|
static std::atomic<size_t> handle(1);
|
|
return reinterpret_cast<sourcekitd_request_handle_t>(
|
|
handle.fetch_add(1, std::memory_order_relaxed));
|
|
}
|
|
|
|
static xpc_connection_t getGlobalConnection();
|
|
|
|
static bool ConnectionInterrupted = false;
|
|
|
|
sourcekitd_response_t sourcekitd_send_request_sync(sourcekitd_object_t req) {
|
|
LOG_SECTION("sourcekitd_send_request_sync-before", InfoHighPrio) {
|
|
// Requests will be printed in Requests.cpp, print them out here as well.
|
|
sourcekitd::printRequestObject(req, Log->getOS());
|
|
}
|
|
|
|
if (ConnectionInterrupted) {
|
|
LOG_WARN_FUNC("request dropped while restoring service");
|
|
return sourcekitd::createErrorRequestInterrupted("restoring service");
|
|
}
|
|
|
|
xpc_connection_t Conn = getGlobalConnection();
|
|
xpc_object_t contents = xpc_array_create_empty();
|
|
xpc_array_append_value(contents, req);
|
|
|
|
xpc_object_t msg = xpc_dictionary_create_empty();
|
|
xpc_dictionary_set_value(msg, xpc::KeyMsg, contents);
|
|
xpc_release(contents);
|
|
|
|
xpc_object_t reply = xpc_connection_send_message_with_reply_sync(Conn, msg);
|
|
xpc_release(msg);
|
|
if (xpc_get_type(reply) == XPC_TYPE_ERROR)
|
|
return reply;
|
|
|
|
assert(xpc_get_type(reply) == XPC_TYPE_DICTIONARY);
|
|
sourcekitd_object_t resp =
|
|
xpc_dictionary_get_value(reply, xpc::KeyMsgResponse);
|
|
xpc_retain(resp);
|
|
xpc_release(reply);
|
|
|
|
LOG_SECTION("sourcekitd_send_request_sync-after", InfoHighPrio) {
|
|
// Responses will be printed in Requests.cpp (with medium prio), print them
|
|
// out here as well.
|
|
if (Logger::isLoggingEnabledForLevel(Logger::Level::InfoMediumPrio))
|
|
sourcekitd::printResponse(resp, Log->getOS());
|
|
}
|
|
|
|
return resp;
|
|
}
|
|
|
|
void sourcekitd_send_request(sourcekitd_object_t req,
|
|
sourcekitd_request_handle_t *out_handle,
|
|
sourcekitd_response_receiver_t receiver) {
|
|
sourcekitd_request_handle_t request_handle = nullptr;
|
|
if (out_handle) {
|
|
request_handle = create_request_handle();
|
|
*out_handle = request_handle;
|
|
}
|
|
|
|
LOG_SECTION("sourcekitd_send_request-before", InfoHighPrio) {
|
|
// Requests will be printed in Requests.cpp, print them out here as well.
|
|
sourcekitd::printRequestObject(req, Log->getOS());
|
|
}
|
|
|
|
if (ConnectionInterrupted) {
|
|
LOG_WARN_FUNC("request dropped while restoring service");
|
|
receiver(sourcekitd::createErrorRequestInterrupted("restoring service"));
|
|
return;
|
|
}
|
|
|
|
xpc_connection_t Conn = getGlobalConnection();
|
|
xpc_object_t contents = xpc_array_create_empty();
|
|
xpc_array_append_value(contents, req);
|
|
|
|
xpc_object_t msg = xpc_dictionary_create_empty();
|
|
xpc_dictionary_set_value(msg, xpc::KeyMsg, contents);
|
|
if (request_handle) {
|
|
xpc_dictionary_set_uint64(msg, xpc::KeyCancelToken,
|
|
reinterpret_cast<uint64_t>(request_handle));
|
|
}
|
|
xpc_release(contents);
|
|
|
|
dispatch_queue_t queue
|
|
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
|
xpc_connection_send_message_with_reply(Conn, msg, queue,
|
|
^(xpc_object_t reply) {
|
|
sourcekitd_response_t Resp = nullptr;
|
|
if (xpc_get_type(reply) == XPC_TYPE_ERROR) {
|
|
Resp = reply;
|
|
} else {
|
|
assert(xpc_get_type(reply) == XPC_TYPE_DICTIONARY);
|
|
Resp = xpc_dictionary_get_value(reply, xpc::KeyMsgResponse);
|
|
}
|
|
// In sourcekitd, the receiver accepts ownership of the response.
|
|
xpc_retain(Resp);
|
|
|
|
LOG_SECTION("sourcekitd_send_request-after", InfoHighPrio) {
|
|
// Responses will be printed in Requests.cpp (with medium prio),
|
|
// print them out here as well.
|
|
if (Logger::isLoggingEnabledForLevel(Logger::Level::InfoMediumPrio))
|
|
sourcekitd::printResponse(Resp, Log->getOS());
|
|
}
|
|
|
|
receiver(Resp);
|
|
});
|
|
xpc_release(msg);
|
|
}
|
|
|
|
void sourcekitd_cancel_request(sourcekitd_request_handle_t handle) {
|
|
xpc_connection_t conn = getGlobalConnection();
|
|
xpc_object_t msg = xpc_dictionary_create_empty();
|
|
xpc_dictionary_set_uint64(msg, xpc::KeyCancelRequest,
|
|
reinterpret_cast<uint64_t>(handle));
|
|
|
|
xpc_connection_send_message(conn, msg);
|
|
|
|
xpc_release(msg);
|
|
}
|
|
|
|
void sourcekitd_request_handle_dispose(sourcekitd_request_handle_t handle) {
|
|
xpc_connection_t conn = getGlobalConnection();
|
|
xpc_object_t msg = xpc_dictionary_create_empty();
|
|
xpc_dictionary_set_uint64(msg, xpc::KeyDisposeRequestHandle,
|
|
reinterpret_cast<uint64_t>(handle));
|
|
|
|
xpc_connection_send_message(conn, msg);
|
|
|
|
xpc_release(msg);
|
|
}
|
|
|
|
static std::vector<std::string> registeredClientPlugins;
|
|
static std::vector<std::string> registeredServicePlugins;
|
|
|
|
/// To avoid repeated crashes, used to notify the service to delay typechecking
|
|
/// in the editor for a certain amount of seconds.
|
|
static std::atomic<size_t> SemanticEditorDelaySecondsNum;
|
|
|
|
static void handleInternalInitRequest(xpc_object_t reply) {
|
|
size_t Delay = SemanticEditorDelaySecondsNum;
|
|
if (Delay != 0)
|
|
xpc_dictionary_set_uint64(reply, xpc::KeySemaEditorDelay, Delay);
|
|
|
|
if (!registeredServicePlugins.empty()) {
|
|
auto plugins = xpc_array_create(nullptr, 0);
|
|
for (const auto &plugin : registeredServicePlugins)
|
|
xpc_array_set_string(plugins, XPC_ARRAY_APPEND, plugin.c_str());
|
|
xpc_dictionary_set_value(reply, xpc::KeyPlugins, plugins);
|
|
xpc_release(plugins);
|
|
}
|
|
}
|
|
|
|
static void handleInternalUIDRequest(xpc_object_t XVal,
|
|
xpc_object_t reply) {
|
|
xpc_object_t response = nullptr;
|
|
if (xpc_get_type(XVal) == XPC_TYPE_STRING) {
|
|
const char *Str = xpc_string_get_string_ptr(XVal);
|
|
LOG_INFO_FUNC(Low, "service queried UID for: " << Str);
|
|
sourcekitd_uid_t SKDUID = sourcekitd_uid_get_from_cstr(Str);
|
|
response = xpc_uint64_create(uintptr_t(SKDUID));
|
|
|
|
} else if (xpc_get_type(XVal) == XPC_TYPE_UINT64) {
|
|
uint64_t Val = xpc_uint64_get_value(XVal);
|
|
sourcekitd_uid_t SKDUID = reinterpret_cast<sourcekitd_uid_t>(Val);
|
|
const char *Str = sourcekitd_uid_get_string_ptr(SKDUID);
|
|
LOG_INFO_FUNC(Low, "service queried string of UID: " << Str);
|
|
response = xpc_string_create(Str);
|
|
|
|
} else {
|
|
llvm::report_fatal_error("Unknown internal message");
|
|
}
|
|
|
|
xpc_dictionary_set_value(reply, xpc::KeyMsgResponse, response);
|
|
xpc_release(response);
|
|
}
|
|
|
|
static void handleInterruptedConnection(xpc_object_t event, xpc_connection_t conn);
|
|
|
|
static void initializeXPCClient() {
|
|
assert(!GlobalConn);
|
|
GlobalConn = xpc_connection_create(SOURCEKIT_XPCSERVICE_IDENTIFIER, nullptr);
|
|
|
|
xpc_connection_set_event_handler(GlobalConn, ^(xpc_object_t event) {
|
|
xpc_type_t type = xpc_get_type(event);
|
|
|
|
if (type == XPC_TYPE_ERROR) {
|
|
if (event == XPC_ERROR_CONNECTION_INTERRUPTED) {
|
|
// The service has crashed. The XPC connection is still valid and
|
|
// sending a message to it will re-launch the service.
|
|
LOG_WARN("connection-event-handler", "Connection interrupt");
|
|
handleInterruptedConnection(event, GlobalConn);
|
|
|
|
} else if (event == XPC_ERROR_CONNECTION_INVALID) {
|
|
// Client initiated shutdown.
|
|
LOG_INFO("connection-event-handler", High,
|
|
"connection invalid error (shutdown)");
|
|
xpc_release(GlobalConn);
|
|
GlobalConn = nullptr;
|
|
|
|
} else {
|
|
LOG_WARN("connection-event-handler",
|
|
"Received unexpected error event: " <<
|
|
xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
|
|
|
|
}
|
|
} else {
|
|
xpc_object_t contents = xpc_dictionary_get_value(event, xpc::KeyInternalMsg);
|
|
if (!contents) {
|
|
llvm::report_fatal_error("Received unexpected message from service");
|
|
}
|
|
|
|
xpc::Message msg = (xpc::Message)xpc_array_get_uint64(contents, 0);
|
|
switch (msg) {
|
|
case xpc::Message::Initialization: {
|
|
xpc_object_t reply = xpc_dictionary_create_reply(event);
|
|
handleInternalInitRequest(reply);
|
|
xpc_connection_send_message(GlobalConn, reply);
|
|
xpc_release(reply);
|
|
break;
|
|
}
|
|
|
|
case xpc::Message::Notification: {
|
|
xpc_object_t notif_contents = xpc_retain(xpc_array_get_value(contents, 1));
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (NotificationReceiver != nullptr) {
|
|
// The receiver accepts ownership of the notification object.
|
|
NotificationReceiver(xpc_retain(notif_contents));
|
|
}
|
|
xpc_release(notif_contents);
|
|
});
|
|
break;
|
|
}
|
|
|
|
case xpc::Message::UIDSynchronization: {
|
|
xpc_object_t reply = xpc_dictionary_create_reply(event);
|
|
handleInternalUIDRequest(xpc_array_get_value(contents, 1), reply);
|
|
xpc_connection_send_message(GlobalConn, reply);
|
|
xpc_release(reply);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
xpc_connection_resume(GlobalConn);
|
|
}
|
|
|
|
void sourcekitd_load_client_plugins(void) {
|
|
static std::once_flag flag;
|
|
std::call_once(flag, [] {
|
|
sourcekitd::PluginInitParams pluginParams(
|
|
/*isClientOnly=*/true, /*registerRequestHandler=*/nullptr,
|
|
/*registerCancellationHandler=*/nullptr);
|
|
loadPlugins(registeredClientPlugins, pluginParams);
|
|
});
|
|
}
|
|
|
|
void sourcekitd_initialize(void) {
|
|
if (sourcekitd::initializeClient()) {
|
|
LOG_INFO_FUNC(High, "initializing");
|
|
initializeXPCClient();
|
|
sourcekitd_load_client_plugins();
|
|
}
|
|
}
|
|
|
|
void sourcekitd_shutdown(void) {
|
|
if (sourcekitd::shutdownClient()) {
|
|
LOG_INFO_FUNC(High, "shutting down");
|
|
assert(GlobalConn);
|
|
xpc_connection_cancel(GlobalConn);
|
|
}
|
|
}
|
|
|
|
void sourcekitd_register_plugin_path(const char *clientPlugin,
|
|
const char *servicePlugin) {
|
|
assert(!GlobalConn && "plugin registered after sourcekitd_initialize");
|
|
if (clientPlugin)
|
|
registeredClientPlugins.push_back(clientPlugin);
|
|
if (servicePlugin)
|
|
registeredServicePlugins.push_back(servicePlugin);
|
|
}
|
|
|
|
static xpc_connection_t getGlobalConnection() {
|
|
assert(GlobalConn);
|
|
return GlobalConn;
|
|
}
|
|
|
|
/// Receives a +1 reference of the connection.
|
|
static void pingService(xpc_connection_t ping_conn) {
|
|
LOG_WARN_FUNC("pinging service");
|
|
|
|
xpc_object_t ping_msg = xpc_dictionary_create_empty();
|
|
xpc_dictionary_set_bool(ping_msg, "ping", true);
|
|
|
|
dispatch_queue_t queue
|
|
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
|
xpc_connection_send_message_with_reply(ping_conn, ping_msg, queue,
|
|
^(xpc_object_t reply) {
|
|
xpc_type_t type = xpc_get_type(reply);
|
|
|
|
if (type == XPC_TYPE_ERROR) {
|
|
if (reply == XPC_ERROR_CONNECTION_INTERRUPTED) {
|
|
// Try again.
|
|
pingService(ping_conn);
|
|
|
|
} else if (reply == XPC_ERROR_CONNECTION_INVALID) {
|
|
// Client initiated shutdown.
|
|
LOG_WARN("ping-event-handler", "connection invalid error");
|
|
xpc_release(ping_conn);
|
|
|
|
} else {
|
|
LOG_WARN("ping-event-handler",
|
|
"Received unexpected error reply: " <<
|
|
xpc_dictionary_get_string(reply, XPC_ERROR_KEY_DESCRIPTION));
|
|
// Try again.
|
|
pingService(ping_conn);
|
|
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
LOG_WARN("ping-event-handler", "service restored");
|
|
ConnectionInterrupted = false;
|
|
|
|
// Create an empty response as notification that the service is restored.
|
|
sourcekitd::ResponseBuilder RespBuilder;
|
|
xpc_object_t contents = RespBuilder.createResponse();
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (NotificationReceiver != nullptr) {
|
|
// The receiver accepts ownership of the notification object.
|
|
xpc_object_t notif_contents = xpc_retain(contents);
|
|
NotificationReceiver(notif_contents);
|
|
}
|
|
xpc_release(contents);
|
|
});
|
|
|
|
xpc_release(ping_conn);
|
|
});
|
|
|
|
xpc_release(ping_msg);
|
|
}
|
|
|
|
static void sendNotification(xpc_object_t event) {
|
|
event = xpc_retain(event);
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (NotificationReceiver != nullptr) {
|
|
// The receiver accepts ownership of the notification object.
|
|
xpc_object_t err_event = xpc_retain(event);
|
|
NotificationReceiver(err_event);
|
|
}
|
|
xpc_release(event);
|
|
});
|
|
}
|
|
|
|
static void updateSemanticEditorDelay() {
|
|
using namespace std::chrono;
|
|
using TimePoint = time_point<system_clock, nanoseconds>;
|
|
|
|
// This minimum is chosen to keep us from being throttled by XPC.
|
|
static const size_t MinDelaySeconds = 10;
|
|
static const size_t MaxDelaySeconds = 20;
|
|
|
|
// Clear any previous setting.
|
|
SemanticEditorDelaySecondsNum = 0;
|
|
|
|
static TimePoint gPrevCrashTime;
|
|
|
|
TimePoint PrevTime = gPrevCrashTime;
|
|
TimePoint CurrTime = system_clock::now();
|
|
gPrevCrashTime = CurrTime;
|
|
|
|
auto Diff = duration_cast<seconds>(CurrTime - PrevTime);
|
|
size_t Delay = Diff.count()*2 + 1;
|
|
if (Diff.count() > 30)
|
|
Delay = 0; // Treat this as more likely unrelated to the previous crash.
|
|
Delay = std::min(std::max(Delay, MinDelaySeconds), MaxDelaySeconds);
|
|
|
|
LOG_WARN_FUNC("disabling semantic editor for " << Delay << " seconds");
|
|
SemanticEditorDelaySecondsNum = Delay;
|
|
|
|
// Notify the client that semantic functionality is disabled.
|
|
ResponseBuilder RespBuilder;
|
|
auto Dict = RespBuilder.getDictionary();
|
|
Dict.set(gKeyNotification, gSemaDisableNotificationUID);
|
|
Dict.set(gKeyDuration, Delay);
|
|
sourcekitd_response_t SemaDisableNotification = RespBuilder.createResponse();
|
|
sendNotification(SemaDisableNotification);
|
|
sourcekitd_response_dispose(SemaDisableNotification);
|
|
}
|
|
|
|
static void handleInterruptedConnection(xpc_object_t event, xpc_connection_t conn) {
|
|
ConnectionInterrupted = true;
|
|
|
|
updateSemanticEditorDelay();
|
|
|
|
// FIXME: InterruptedConnectionHandler will go away.
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (InterruptedConnectionHandler != nullptr) {
|
|
InterruptedConnectionHandler();
|
|
}
|
|
});
|
|
// Send the disconnect error as notification.
|
|
sendNotification(event);
|
|
|
|
// Retain connection while we try to ping it.
|
|
// Since this happens implicitly, we can't blame the client if it shuts down
|
|
// while we are trying to ping.
|
|
pingService((xpc_connection_t)xpc_retain(conn));
|
|
}
|