From 63a39d4c9bc2f855077f860c1bdeb8af8c018da5 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Wed, 15 Jan 2025 14:19:33 -0500 Subject: [PATCH] ProxyClientBase: avoid static_cast to partially destructed object This is a bugfix that should fix the UBSan failure reported https://github.com/chaincodelabs/libmultiprocess/issues/125 In practice there is no real bug here, just like there was no real bug fixed in the recent "ProxyClientBase: avoid static_cast to partially constructed object" change in https://github.com/chaincodelabs/libmultiprocess/pull/121. This is because in practice, ProxyClient subclasses only inherit ProxyClientBase state and do not define any state of their own, so there is nothing bad that could actually happen from static_cast-ing a ProxyClientBase pointer to a ProxyClient pointer inside the ProxyClientBase constructor or destructor. However, using static_cast this way technically triggers undefined behavior, and that causes UBSan to fail, so this commit drops the static_cast by making ProxyClient::construct() and ProxyClient::destroy() methods into static methods taking a ProxyClientBase& argument instead of instance methods using *this. Fixes #125 --- include/mp/proxy-io.h | 7 ++++--- include/mp/proxy.h | 8 ++++---- src/mp/gen.cpp | 15 +++++++++------ 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/include/mp/proxy-io.h b/include/mp/proxy-io.h index 29cd3a45..9b3c876f 100644 --- a/include/mp/proxy-io.h +++ b/include/mp/proxy-io.h @@ -382,7 +382,7 @@ ProxyClientBase::ProxyClientBase(typename Interface::Client cli auto cleanup = m_context.connection->addSyncCleanup([this]() { // Release client capability by move-assigning to temporary. { - typename Interface::Client(std::move(self().m_client)); + typename Interface::Client(std::move(m_client)); } { std::unique_lock lock(m_context.connection->m_loop.m_mutex); @@ -412,13 +412,13 @@ ProxyClientBase::ProxyClientBase(typename Interface::Client cli // the remote object, waiting for it to be deleted server side. If the // capnp interface does not define a destroy method, this will just call // an empty stub defined in the ProxyClientBase class and do nothing. - self().destroy(); + Sub::destroy(*this); // FIXME: Could just invoke removed addCleanup fn here instead of duplicating code m_context.connection->m_loop.sync([&]() { // Release client capability by move-assigning to temporary. { - typename Interface::Client(std::move(self().m_client)); + typename Interface::Client(std::move(m_client)); } { std::unique_lock lock(m_context.connection->m_loop.m_mutex); @@ -432,6 +432,7 @@ ProxyClientBase::ProxyClientBase(typename Interface::Client cli }); } }); + Sub::construct(*this); } template diff --git a/include/mp/proxy.h b/include/mp/proxy.h index 1ee1c753..e1933b9c 100644 --- a/include/mp/proxy.h +++ b/include/mp/proxy.h @@ -56,6 +56,8 @@ class ProxyClientBase : public Impl_ public: using Interface = Interface_; using Impl = Impl_; + using Sub = ProxyClient; + using Super = ProxyClientBase; ProxyClientBase(typename Interface::Client client, Connection* connection, bool destroy_connection); ~ProxyClientBase() noexcept; @@ -89,10 +91,8 @@ class ProxyClientBase : public Impl_ // then it will also ensure that the destructor runs on the same thread the // client used to make other RPC calls, instead of running on the server // EventLoop thread and possibly blocking it. - void construct() {} - void destroy() {} - - ProxyClient& self() { return static_cast&>(*this); } + static void construct(Super&) {} + static void destroy(Super&) {} typename Interface::Client m_client; ProxyContext m_context; diff --git a/src/mp/gen.cpp b/src/mp/gen.cpp index 007d2362..93b001f6 100644 --- a/src/mp/gen.cpp +++ b/src/mp/gen.cpp @@ -357,8 +357,7 @@ void Generate(kj::StringPtr src_prefix, client << "public ProxyClientCustom<" << message_namespace << "::" << node_name << ", " << proxied_class_type << ">\n{\n"; client << "public:\n"; - client << " template \n"; - client << " ProxyClient(Args&&... args) : ProxyClientCustom(std::forward(args)...) { construct(); }\n"; + client << " using ProxyClientCustom::ProxyClientCustom;\n"; client << " ~ProxyClient();\n"; std::ostringstream server; @@ -531,18 +530,22 @@ void Generate(kj::StringPtr src_prefix, server_invoke_end << ")"; } + std::string static_str{is_construct || is_destroy ? "static " : ""}; + std::string super_str{is_construct || is_destroy ? "Super& super" : ""}; + std::string self_str{is_construct || is_destroy ? "super" : "*this"}; + client << " using M" << method_ordinal << " = ProxyClientMethodTraits<" << method_prefix << "Params>;\n"; - client << " typename M" << method_ordinal << "::Result " << method_name << "(" - << client_args.str() << ")"; + client << " " << static_str << "typename M" << method_ordinal << "::Result " << method_name << "(" + << super_str << client_args.str() << ")"; client << ";\n"; def_client << "ProxyClient<" << message_namespace << "::" << node_name << ">::M" << method_ordinal << "::Result ProxyClient<" << message_namespace << "::" << node_name << ">::" << method_name - << "(" << client_args.str() << ") {\n"; + << "(" << super_str << client_args.str() << ") {\n"; if (has_result) { def_client << " typename M" << method_ordinal << "::Result result;\n"; } - def_client << " clientInvoke(*this, &" << message_namespace << "::" << node_name + def_client << " clientInvoke(" << self_str << ", &" << message_namespace << "::" << node_name << "::Client::" << method_name << "Request" << client_invoke.str() << ");\n"; if (has_result) def_client << " return result;\n"; def_client << "}\n";