diff --git a/include/Server/Plugin.h b/include/Server/Plugin.h index 7accb9f2..4293e293 100644 --- a/include/Server/Plugin.h +++ b/include/Server/Plugin.h @@ -1,6 +1,7 @@ #pragma once #include +#include "PluginProtocol.h" #include "Async/Async.h" #include "llvm/ADT/ArrayRef.h" @@ -9,33 +10,22 @@ // clang-format off /// Run `python scripts/plugin-def.py update` to update the hash. -#define CLICE_PLUGIN_DEF_HASH "sha256:e35ff1adfbc385f7bae605e82ca4e7e70cfbf0f933bb8d57169d0d29abe5b6f7" +#define CLICE_PLUGIN_DEF_HASH "sha256:c46f7edfda0455327c65d40b9315ad5dc39153326c8cc63f1d8de2e2d0e7735a" // clang-format on namespace clice { +/// The hash of the definitions exposed to server plugins. +constexpr std::string_view plugin_definition_hash = CLICE_PLUGIN_DEF_HASH; + class Server; -#define CLICE_PLUGIN_PROTOCOL - -#include "PluginDef.h" -#undef CLICE_PLUGIN_PROTOCOL - struct ServerPluginBuilder; /// A loaded server plugin. /// /// An instance of this class wraps a loaded server plugin and gives access to its interface. class Plugin { -public: - struct Self; - - Plugin(Self* self) : self(self) {} - - Self* operator->() { - return self; - } - public: /// Attempts to load a server plugin from a given file. /// @@ -56,21 +46,17 @@ public: /// Registers the server callbacks for the loaded plugin. void register_server_callbacks(ServerPluginBuilder& builder) const; +public: + struct Self; + + Plugin(Self* self) : self(self) {} + + Self* operator->() { + return self; + } + protected: Self* self; }; -struct ServerPluginBuilder { -public: - ServerPluginBuilder(ServerRef server_ref) : server_ref(server_ref) {} - -#define CliceServerPluginAPI(METHOD, ...) void METHOD(void* plugin_data, __VA_ARGS__) - -#include "PluginDef.h" -#undef CliceServerPluginAPI - -protected: - ServerRef server_ref; -}; - } // namespace clice diff --git a/include/Server/PluginDef.h b/include/Server/PluginProtocol.h similarity index 53% rename from include/Server/PluginDef.h rename to include/Server/PluginProtocol.h index 36d7dc4b..f6947dda 100644 --- a/include/Server/PluginDef.h +++ b/include/Server/PluginProtocol.h @@ -10,14 +10,23 @@ #error "CLICE_PLUGIN_API_VERSION must be 1, but got " CLICE_PLUGIN_API_VERSION #endif -/// Defines the library APIs that loads a plugin. -#ifdef CLICE_PLUGIN_PROTOCOL +#include + +#include "Async/Async.h" + +#include "llvm/Support/Compiler.h" + +namespace clice { + +class Server; + struct ServerPluginBuilder; +/// Defines the library APIs that loads a plugin. extern "C" { /// A C-compatible struct that contains information about the plugin. struct PluginInfo { /// The clice API version of the plugin. - std::uint32_t api_version; + uint32_t api_version; /// The name of the plugin. const char* name; /// The version of the plugin. @@ -61,23 +70,43 @@ public: protected: Self* self; }; -#endif /// Defines the library APIs to register callbacks for a plugin. -#ifdef CliceServerPluginAPI -CliceServerPluginAPI(get_server_ref, ServerRef& server); -using lifecycle_hook_t = async::Task<> (*)(ServerRef server, void* plugin_data); +struct ServerPluginBuilder { +public: + ServerPluginBuilder(ServerRef server_ref) : server_ref(server_ref) {} -CliceServerPluginAPI(on_initialize, lifecycle_hook_t callback); -CliceServerPluginAPI(on_initialized, lifecycle_hook_t callback); -CliceServerPluginAPI(on_shutdown, lifecycle_hook_t callback); -CliceServerPluginAPI(on_exit, lifecycle_hook_t callback); -CliceServerPluginAPI(on_did_change_configuration, lifecycle_hook_t callback); -using command_handler_t = - async::Task (*)(ServerRef server, - void* plugin_data, - llvm::ArrayRef arguments); -CliceServerPluginAPI(register_commmand_handler, - llvm::StringRef command, - command_handler_t callback); -#endif + /// Gets a reference to the server. + auto get_server_ref() const -> ServerRef { + return server_ref; + } + +#define CliceServerPluginAPI(METHOD, ...) void METHOD(void* plugin_data, __VA_ARGS__) + + using lifecycle_hook_t = async::Task<> (*)(ServerRef server, void* plugin_data); + + /// Registers a callback to be called when the server is initialized. + CliceServerPluginAPI(on_initialize, lifecycle_hook_t callback); + /// Registers a callback to be called when the server is initialized. + CliceServerPluginAPI(on_initialized, lifecycle_hook_t callback); + /// Registers a callback to be called when the server is shutdown. + CliceServerPluginAPI(on_shutdown, lifecycle_hook_t callback); + /// Registers a callback to be called when the server is exiting. + CliceServerPluginAPI(on_exit, lifecycle_hook_t callback); + /// Registers a callback to be called when the server's configuration is changed. + CliceServerPluginAPI(on_did_change_configuration, lifecycle_hook_t callback); + using command_handler_t = + async::Task (*)(ServerRef server, + void* plugin_data, + llvm::ArrayRef arguments); + /// Registers a callback to be called when a command is received from the LSP client. + CliceServerPluginAPI(register_commmand_handler, + llvm::StringRef command, + command_handler_t callback); +#undef CliceServerPluginAPI + +protected: + ServerRef server_ref; +}; + +} // namespace clice diff --git a/include/Server/Utility.h b/include/Server/Utility.h new file mode 100644 index 00000000..f4ddd06d --- /dev/null +++ b/include/Server/Utility.h @@ -0,0 +1,3 @@ +#pragma once + +#define bail(...) std::unexpected(std::format(__VA_ARGS__)) diff --git a/src/Server/Plugin.cpp b/src/Server/Plugin.cpp index 8e1cfaff..daf02dbd 100644 --- a/src/Server/Plugin.cpp +++ b/src/Server/Plugin.cpp @@ -1,6 +1,7 @@ #include "Server/Plugin.h" #include "Implement.h" +#include "Server/Utility.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/Support/DynamicLibrary.h" @@ -28,44 +29,62 @@ std::expected Plugin::load(const std::string& file_path) { std::string err; auto library = llvm::sys::DynamicLibrary::getPermanentLibrary(file_path.c_str(), &err); if(!library.isValid()) { - return std::unexpected("Could not load library '" + file_path + "': " + err); + return bail("Could not load library '{}': {}", file_path, err); } Plugin P{ + /// We currently never destroy plugins, so this is not a memory leak. new Self{file_path, library} }; /// `clice_get_server_plugin_info` should be resolved to the definition from the plugin /// we are currently loading. intptr_t get_details_fn = (intptr_t)library.getAddressOfSymbol("clice_get_server_plugin_info"); - if(!get_details_fn) { - /// If the symbol isn't found, this is probably a legacy plugin, which is an - /// error. - return std::unexpected("Plugin entry point not found in '" + file_path + - "'. Is this a clice server plugin?"); + return bail( + "The symbol `clice_get_server_plugin_info` is not found in '{}'. Is this a clice server plugin?", + file_path); } auto info = reinterpret_cast(get_details_fn)(); /// First, we check whether the plugin is compatible with the clice plugin API. if(info.api_version != CLICE_PLUGIN_API_VERSION) { - return std::unexpected("Wrong API version on plugin '" + file_path + "'. Got version " + - std::to_string(info.api_version) + ", supported version is " + - std::to_string(CLICE_PLUGIN_API_VERSION) + "."); + return bail("Wrong API version on plugin '{}'. Got version {}. Supported version is {}.", + file_path, + info.api_version, + CLICE_PLUGIN_API_VERSION); } /// Then, we safely get definition hash from the plugin, and check if it is consistent with - /// the expected hash. This ensures that the plugin has consistent declarations with the server. + /// the expected hash. This ensures that the plugin has consistent declarations with the + /// server. std::string definition_hash = info.definition_hash; - if(definition_hash != CLICE_PLUGIN_DEF_HASH) { - return std::unexpected("Wrong definition hash on plugin '" + file_path + "'. Got '" + - definition_hash + "', expected '" + CLICE_PLUGIN_DEF_HASH + "'."); + if(plugin_definition_hash.size() != definition_hash.size()) { + return bail("Wrong definition hash size on plugin '{}'. Got {}, expected {} ({}).", + file_path, + definition_hash.size(), + plugin_definition_hash.size(), + plugin_definition_hash); } + /// If there is any non-printable character in the definition hash, this is likely a bug in the + /// plugin. We cannot even print the `definition_hash` in this case. + if(std::ranges::any_of(definition_hash, [](char c) { return !std::isprint(c); })) { + return bail("Corrupt definition hash on plugin '{}'. This is likely a bug in the plugin.", + file_path); + } + + if(definition_hash != CLICE_PLUGIN_DEF_HASH) { + return bail("Wrong definition hash on plugin '{}'. Got '{}', expected '{}'.", + file_path, + definition_hash, + CLICE_PLUGIN_DEF_HASH); + } + + /// A plugin must implement the `register_server_callbacks` function. if(!info.register_server_callbacks) { - return std::unexpected("Empty `register_server_callbacks` function in plugin '" + - file_path + "'."); + return bail("Empty `register_server_callbacks` function in plugin '{}'.", file_path); } P->name = info.name; @@ -91,10 +110,6 @@ using command_handler_t = async::Task (*)(ServerRef server, const llvm::ArrayRef& arguments); -void ServerPluginBuilder::get_server_ref(void* plugin_data, ServerRef& server) { - server = server_ref; -} - void ServerPluginBuilder::on_initialize(void* plugin_data, lifecycle_hook_t callback) { auto server = server_ref; server_ref.server().initialize_hooks.push_back(