| //===--- sourcekitd.cpp ---------------------------------------------------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See http://swift.org/LICENSE.txt for license information |
| // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "sourcekitd/Internal-XPC.h" |
| #include "SourceKit/Support/Logging.h" |
| #include "SourceKit/Support/Tracing.h" |
| #include "SourceKit/Support/UIdent.h" |
| |
| #include "llvm/Support/ErrorHandling.h" |
| #include "llvm/Support/Mutex.h" |
| #include "llvm/Support/TimeValue.h" |
| #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"); |
| |
| void handleTraceMessageRequest(uint64_t Session, xpc_object_t Msg); |
| void initializeTracing(); |
| void persistTracingData(); |
| bool isTracingEnabled(); |
| |
| 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 |
| //----------------------------------------------------------------------------// |
| |
| 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(nullptr, 0); |
| xpc_array_append_value(contents, req); |
| |
| xpc_object_t msg = xpc_dictionary_create(nullptr, nullptr, 0); |
| xpc_dictionary_set_value(msg, "msg", 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) { |
| // FIXME: Implement 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(nullptr, 0); |
| xpc_array_append_value(contents, req); |
| |
| xpc_object_t msg = xpc_dictionary_create(nullptr, nullptr, 0); |
| xpc_dictionary_set_value(msg, "msg", contents); |
| 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) { |
| // FIXME: Implement cancelling. |
| } |
| |
| /// 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); |
| xpc_dictionary_set_uint64(reply, xpc::KeyTracingEnabled, isTracingEnabled()); |
| } |
| |
| 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); |
| |
| void sourcekitd::initialize() { |
| initializeTracing(); |
| |
| assert(!GlobalConn); |
| GlobalConn = xpc_connection_create("com.apple.SourceKitService", NULL); |
| |
| 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; |
| } |
| |
| case xpc::Message::TraceMessage: { |
| xpc_object_t reply = xpc_dictionary_create_reply(event); |
| handleTraceMessageRequest(xpc_array_get_uint64(contents, 1), |
| xpc_array_get_value(contents, 2)); |
| xpc_connection_send_message(GlobalConn, reply); |
| xpc_release(reply); |
| break; |
| } |
| } |
| } |
| }); |
| |
| xpc_connection_resume(GlobalConn); |
| } |
| |
| void sourcekitd::shutdown() { |
| assert(GlobalConn); |
| xpc_connection_cancel(GlobalConn); |
| } |
| |
| 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(nullptr, nullptr, 0); |
| 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 llvm::sys; |
| |
| // Clear any previous setting. |
| SemanticEditorDelaySecondsNum = 0; |
| |
| static TimeValue gPrevCrashTime; |
| |
| TimeValue PrevTime = gPrevCrashTime; |
| TimeValue CurrTime = TimeValue::now(); |
| gPrevCrashTime = CurrTime; |
| if (PrevTime == TimeValue()) { |
| // First time that it crashed. |
| return; |
| } |
| |
| TimeValue Diff = CurrTime - PrevTime; |
| if (Diff.seconds() > 30) |
| return; // treat this as more likely unrelated to the previous crash. |
| |
| size_t Delay = std::min(size_t(20), size_t(Diff.seconds()*2 + 1)); |
| 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; |
| |
| persistTracingData(); |
| 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)); |
| } |