diff options
Diffstat (limited to 'binaryurp/source/bridge.cxx')
-rw-r--r-- | binaryurp/source/bridge.cxx | 1054 |
1 files changed, 1054 insertions, 0 deletions
diff --git a/binaryurp/source/bridge.cxx b/binaryurp/source/bridge.cxx new file mode 100644 index 0000000000..523bbbf14a --- /dev/null +++ b/binaryurp/source/bridge.cxx @@ -0,0 +1,1054 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <limits> +#include <memory> +#include <utility> +#include <vector> + +#include <com/sun/star/bridge/InvalidProtocolChangeException.hpp> +#include <com/sun/star/bridge/XBridge.hpp> +#include <com/sun/star/bridge/XInstanceProvider.hpp> +#include <com/sun/star/bridge/XProtocolProperties.hpp> +#include <com/sun/star/connection/XConnection.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/lang/DisposedException.hpp> +#include <com/sun/star/lang/EventObject.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/RuntimeException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/XInterface.hpp> +#include <cppuhelper/exc_hlp.hxx> +#include <cppuhelper/weak.hxx> +#include <osl/mutex.hxx> +#include <osl/thread.hxx> +#include <rtl/byteseq.hxx> +#include <rtl/random.h> +#include <rtl/ref.hxx> +#include <rtl/string.h> +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <sal/types.h> +#include <typelib/typeclass.h> +#include <typelib/typedescription.h> +#include <typelib/typedescription.hxx> +#include <uno/dispatcher.hxx> +#include <uno/environment.hxx> +#include <uno/lbnames.h> + +#include "binaryany.hxx" +#include "bridge.hxx" +#include "bridgefactory.hxx" +#include "incomingreply.hxx" +#include "lessoperators.hxx" +#include "outgoingrequest.hxx" +#include "outgoingrequests.hxx" +#include "proxy.hxx" +#include "reader.hxx" + +namespace binaryurp { + +namespace { + +sal_Int32 random() { + sal_Int32 n; + rtlRandomPool pool = rtl_random_createPool(); + rtl_random_getBytes(pool, &n, sizeof n); + rtl_random_destroyPool(pool); + return n; +} + +OUString toString(css::uno::TypeDescription const & type) { + typelib_TypeDescription * d = type.get(); + assert(d != nullptr && d->pTypeName != nullptr); + return OUString(d->pTypeName); +} + +extern "C" void freeProxyCallback( + SAL_UNUSED_PARAMETER uno_ExtEnvironment *, void * pProxy) +{ + assert(pProxy != nullptr); + static_cast< Proxy * >(pProxy)->do_free(); +} + +bool isThread(salhelper::Thread * thread) { + assert(thread != nullptr); + return osl::Thread::getCurrentIdentifier() == thread->getIdentifier(); +} + +class AttachThread { +public: + explicit AttachThread(uno_ThreadPool threadPool); + + ~AttachThread(); + + const rtl::ByteSequence& getTid() const noexcept { return tid_;} + +private: + AttachThread(const AttachThread&) = delete; + AttachThread& operator=(const AttachThread&) = delete; + + uno_ThreadPool threadPool_; + rtl::ByteSequence tid_; +}; + +AttachThread::AttachThread(uno_ThreadPool threadPool): threadPool_(threadPool) { + sal_Sequence * s = nullptr; + uno_getIdOfCurrentThread(&s); + tid_ = rtl::ByteSequence(s, rtl::BYTESEQ_NOACQUIRE); + uno_threadpool_attach(threadPool_); +} + +AttachThread::~AttachThread() { + uno_threadpool_detach(threadPool_); + uno_releaseIdFromCurrentThread(); +} + + +class PopOutgoingRequest { +public: + PopOutgoingRequest( + OutgoingRequests & requests, rtl::ByteSequence tid, + OutgoingRequest const & request); + + ~PopOutgoingRequest(); + + void clear(); + +private: + PopOutgoingRequest(const PopOutgoingRequest&) = delete; + PopOutgoingRequest& operator=(const PopOutgoingRequest&) = delete; + + OutgoingRequests & requests_; + rtl::ByteSequence tid_; + bool cleared_; +}; + +PopOutgoingRequest::PopOutgoingRequest( + OutgoingRequests & requests, rtl::ByteSequence tid, + OutgoingRequest const & request): + requests_(requests), tid_(std::move(tid)), cleared_(false) +{ + requests_.push(tid_, request); +} + +PopOutgoingRequest::~PopOutgoingRequest() { + if (!cleared_) { + requests_.pop(tid_); + } +} + +void PopOutgoingRequest::clear() { + cleared_ = true; +} + +} + +struct Bridge::SubStub { + com::sun::star::uno::UnoInterfaceReference object; + + sal_uInt32 references; +}; + +Bridge::Bridge( + rtl::Reference< BridgeFactory > const & factory, OUString name, + css::uno::Reference< css::connection::XConnection > const & connection, + css::uno::Reference< css::bridge::XInstanceProvider > provider): + factory_(factory), name_(std::move(name)), connection_(connection), + provider_(std::move(provider)), + binaryUno_(UNO_LB_UNO), + cppToBinaryMapping_(CPPU_CURRENT_LANGUAGE_BINDING_NAME, UNO_LB_UNO), + binaryToCppMapping_(UNO_LB_UNO, CPPU_CURRENT_LANGUAGE_BINDING_NAME), + protPropTid_( + reinterpret_cast< sal_Int8 const * >(".UrpProtocolPropertiesTid"), + RTL_CONSTASCII_LENGTH(".UrpProtocolPropertiesTid")), + protPropOid_("UrpProtocolProperties"), + protPropType_( + cppu::UnoType< + css::uno::Reference< css::bridge::XProtocolProperties > >::get()), + protPropRequest_("com.sun.star.bridge.XProtocolProperties::requestChange"), + protPropCommit_("com.sun.star.bridge.XProtocolProperties::commitChange"), + state_(STATE_INITIAL), threadPool_(nullptr), currentContextMode_(false), + proxies_(0), calls_(0), normalCall_(false), activeCalls_(0), + mode_(MODE_REQUESTED) +{ + assert(factory.is() && connection.is()); + if (!binaryUno_.is()) { + throw css::uno::RuntimeException("URP: no binary UNO environment"); + } + if (!(cppToBinaryMapping_.is() && binaryToCppMapping_.is())) { + throw css::uno::RuntimeException("URP: no C++ UNO mapping"); + } + passive_.set(); + // coverity[uninit_member] - random_ is set in due course by the reader_ thread's state machine +} + +void Bridge::start() { + rtl::Reference r(new Reader(this)); + rtl::Reference w(new Writer(this)); + { + std::lock_guard g(mutex_); + assert( + state_ == STATE_INITIAL && threadPool_ == nullptr && !writer_.is() && + !reader_.is()); + threadPool_ = uno_threadpool_create(); + assert(threadPool_ != nullptr); + reader_ = r; + writer_ = w; + state_ = STATE_STARTED; + } + // It is important to call reader_->launch() last here; both + // Writer::execute and Reader::execute can call Bridge::terminate, but + // Writer::execute is initially blocked in unblocked_.wait() until + // Reader::execute has called bridge_->sendRequestChangeRequest(), so + // effectively only reader_->launch() can lead to an early call to + // Bridge::terminate + w->launch(); + r->launch(); +} + +void Bridge::terminate(bool final) { + uno_ThreadPool tp; + // Make sure function-local variables (Stubs s, etc.) are destroyed before + // the final uno_threadpool_destroy/threadPool_ = 0: + { + rtl::Reference< Reader > r; + rtl::Reference< Writer > w; + bool joinW; + Listeners ls; + { + std::unique_lock g(mutex_); + switch (state_) { + case STATE_INITIAL: // via ~Bridge -> dispose -> terminate + case STATE_FINAL: + return; + case STATE_STARTED: + break; + case STATE_TERMINATED: + if (final) { + g.unlock(); + terminated_.wait(); + { + std::lock_guard g2(mutex_); + tp = threadPool_; + threadPool_ = nullptr; + if (reader_.is()) { + if (!isThread(reader_.get())) { + r = reader_; + } + reader_.clear(); + } + if (writer_.is()) { + if (!isThread(writer_.get())) { + w = writer_; + } + writer_.clear(); + } + state_ = STATE_FINAL; + } + assert(!(r.is() && w.is())); + if (r.is()) { + r->join(); + } else if (w.is()) { + w->join(); + } + if (tp != nullptr) { + uno_threadpool_destroy(tp); + } + } + return; + } + tp = threadPool_; + assert(!(final && isThread(reader_.get()))); + if (!isThread(reader_.get())) { + std::swap(reader_, r); + } + w = writer_; + joinW = !isThread(writer_.get()); + assert(!final || joinW); + if (joinW) { + writer_.clear(); + } + ls.swap(listeners_); + state_ = final ? STATE_FINAL : STATE_TERMINATED; + } + try { + connection_->close(); + } catch (const css::io::IOException & e) { + SAL_INFO("binaryurp", "caught IO exception '" << e << '\''); + } + assert(w.is()); + w->stop(); + if (r.is()) { + r->join(); + } + if (joinW) { + w->join(); + } + assert(tp != nullptr); + uno_threadpool_dispose(tp); + Stubs s; + { + std::lock_guard g(mutex_); + s.swap(stubs_); + } + for (auto & stub : s) + { + for (auto & item : stub.second) + { + SAL_INFO( + "binaryurp", + "stub '" << stub.first << "', '" << toString(item.first) + << "' still mapped at Bridge::terminate"); + binaryUno_.get()->pExtEnv->revokeInterface( + binaryUno_.get()->pExtEnv, item.second.object.get()); + } + } + factory_->removeBridge(this); + for (auto const& listener : ls) + { + try { + listener->disposing( + css::lang::EventObject( + getXWeak())); + } catch (const css::uno::RuntimeException & e) { + SAL_WARN("binaryurp", "caught " << e); + } + } + } + if (final) { + uno_threadpool_destroy(tp); + } + { + std::lock_guard g(mutex_); + if (final) { + threadPool_ = nullptr; + } + } + terminated_.set(); +} + + +BinaryAny Bridge::mapCppToBinaryAny(css::uno::Any const & cppAny) { + css::uno::Any in(cppAny); + BinaryAny out; + out.~BinaryAny(); + uno_copyAndConvertData( + &out.get(), &in, + css::uno::TypeDescription(cppu::UnoType< css::uno::Any >::get()).get(), + cppToBinaryMapping_.get()); + return out; +} + +uno_ThreadPool Bridge::getThreadPool() { + std::lock_guard g(mutex_); + checkDisposed(); + assert(threadPool_ != nullptr); + return threadPool_; +} + +rtl::Reference< Writer > Bridge::getWriter() { + std::lock_guard g(mutex_); + checkDisposed(); + assert(writer_.is()); + return writer_; +} + +css::uno::UnoInterfaceReference Bridge::registerIncomingInterface( + OUString const & oid, css::uno::TypeDescription const & type) +{ + assert(type.is()); + if (oid.isEmpty()) { + return css::uno::UnoInterfaceReference(); + } + css::uno::UnoInterfaceReference obj(findStub(oid, type)); + if (!obj.is()) { + binaryUno_.get()->pExtEnv->getRegisteredInterface( + binaryUno_.get()->pExtEnv, + reinterpret_cast< void ** >(&obj.m_pUnoI), oid.pData, + reinterpret_cast< typelib_InterfaceTypeDescription * >(type.get())); + if (obj.is()) { + makeReleaseCall(oid, type); + } else { + obj.set(new Proxy(this, oid, type), SAL_NO_ACQUIRE); + { + std::lock_guard g(mutex_); + assert(proxies_ < std::numeric_limits< std::size_t >::max()); + ++proxies_; + } + binaryUno_.get()->pExtEnv->registerProxyInterface( + binaryUno_.get()->pExtEnv, + reinterpret_cast< void ** >(&obj.m_pUnoI), &freeProxyCallback, + oid.pData, + reinterpret_cast< typelib_InterfaceTypeDescription * >( + type.get())); + } + } + return obj; +} + +OUString Bridge::registerOutgoingInterface( + css::uno::UnoInterfaceReference const & object, + css::uno::TypeDescription const & type) +{ + assert(type.is()); + if (!object.is()) { + return OUString(); + } + OUString oid; + if (!Proxy::isProxy(this, object, &oid)) { + binaryUno_.get()->pExtEnv->getObjectIdentifier( + binaryUno_.get()->pExtEnv, &oid.pData, object.get()); + std::lock_guard g(mutex_); + Stubs::iterator i(stubs_.find(oid)); + Stub newStub; + Stub * stub = i == stubs_.end() ? &newStub : &i->second; + Stub::iterator j(stub->find(type)); + //TODO: Release sub-stub if it is not successfully sent to remote side + // (otherwise, stub will leak until terminate()): + if (j == stub->end()) { + j = stub->emplace(type, SubStub()).first; + if (stub == &newStub) { + i = stubs_.emplace(oid, Stub()).first; + std::swap(i->second, newStub); + j = i->second.find(type); + assert(j != i->second.end()); + } + j->second.object = object; + j->second.references = 1; + binaryUno_.get()->pExtEnv->registerInterface( + binaryUno_.get()->pExtEnv, + reinterpret_cast< void ** >(&j->second.object.m_pUnoI), + oid.pData, + reinterpret_cast< typelib_InterfaceTypeDescription * >( + type.get())); + } else { + assert(stub != &newStub); + if (j->second.references == SAL_MAX_UINT32) { + throw css::uno::RuntimeException( + "URP: stub reference count overflow"); + } + ++j->second.references; + } + } + return oid; +} + +css::uno::UnoInterfaceReference Bridge::findStub( + OUString const & oid, css::uno::TypeDescription const & type) +{ + assert(!oid.isEmpty() && type.is()); + std::lock_guard g(mutex_); + Stubs::iterator i(stubs_.find(oid)); + if (i != stubs_.end()) { + Stub::iterator j(i->second.find(type)); + if (j != i->second.end()) { + return j->second.object; + } + for (auto const& item : i->second) + { + if (typelib_typedescription_isAssignableFrom( + type.get(), item.first.get())) + { + return item.second.object; + } + } + } + return css::uno::UnoInterfaceReference(); +} + +void Bridge::releaseStub( + OUString const & oid, css::uno::TypeDescription const & type) +{ + assert(!oid.isEmpty() && type.is()); + css::uno::UnoInterfaceReference obj; + bool unused; + { + std::lock_guard g(mutex_); + Stubs::iterator i(stubs_.find(oid)); + if (i == stubs_.end()) { + throw css::uno::RuntimeException("URP: release unknown stub"); + } + Stub::iterator j(i->second.find(type)); + if (j == i->second.end()) { + throw css::uno::RuntimeException("URP: release unknown stub"); + } + assert(j->second.references > 0); + --j->second.references; + if (j->second.references == 0) { + obj = j->second.object; + i->second.erase(j); + if (i->second.empty()) { + stubs_.erase(i); + } + } + unused = becameUnused(); + } + if (obj.is()) { + binaryUno_.get()->pExtEnv->revokeInterface( + binaryUno_.get()->pExtEnv, obj.get()); + } + terminateWhenUnused(unused); +} + +void Bridge::resurrectProxy(Proxy & proxy) { + uno_Interface * p = &proxy; + binaryUno_.get()->pExtEnv->registerProxyInterface( + binaryUno_.get()->pExtEnv, + reinterpret_cast< void ** >(&p), &freeProxyCallback, + proxy.getOid().pData, + reinterpret_cast< typelib_InterfaceTypeDescription * >( + proxy.getType().get())); + assert(p == &proxy); +} + +void Bridge::revokeProxy(Proxy & proxy) { + binaryUno_.get()->pExtEnv->revokeInterface( + binaryUno_.get()->pExtEnv, &proxy); +} + +void Bridge::freeProxy(Proxy & proxy) { + try { + makeReleaseCall(proxy.getOid(), proxy.getType()); + } catch (const css::uno::RuntimeException & e) { + SAL_INFO( + "binaryurp", "caught runtime exception '" << e << '\''); + } catch (const std::exception & e) { + SAL_WARN("binaryurp", "caught C++ exception '" << e.what() << '\''); + } + bool unused; + { + std::lock_guard g(mutex_); + assert(proxies_ > 0); + --proxies_; + unused = becameUnused(); + } + terminateWhenUnused(unused); +} + +void Bridge::incrementCalls(bool normalCall) noexcept { + std::lock_guard g(mutex_); + assert(calls_ < std::numeric_limits< std::size_t >::max()); + ++calls_; + normalCall_ |= normalCall; +} + +void Bridge::decrementCalls() { + bool unused; + { + std::lock_guard g(mutex_); + assert(calls_ > 0); + --calls_; + unused = becameUnused(); + } + terminateWhenUnused(unused); +} + +void Bridge::incrementActiveCalls() noexcept { + std::lock_guard g(mutex_); + assert( + activeCalls_ <= calls_ && + activeCalls_ < std::numeric_limits< std::size_t >::max()); + ++activeCalls_; + passive_.reset(); +} + +void Bridge::decrementActiveCalls() noexcept { + std::lock_guard g(mutex_); + assert(activeCalls_ <= calls_ && activeCalls_ > 0); + --activeCalls_; + if (activeCalls_ == 0) { + passive_.set(); + } +} + +bool Bridge::makeCall( + OUString const & oid, css::uno::TypeDescription const & member, + bool setter, std::vector< BinaryAny >&& inArguments, + BinaryAny * returnValue, std::vector< BinaryAny > * outArguments) +{ + std::unique_ptr< IncomingReply > resp; + { + uno_ThreadPool tp = getThreadPool(); + AttachThread att(tp); + PopOutgoingRequest pop( + outgoingRequests_, att.getTid(), + OutgoingRequest(OutgoingRequest::KIND_NORMAL, member, setter)); + sendRequest( + att.getTid(), oid, css::uno::TypeDescription(), member, + std::move(inArguments)); + pop.clear(); + incrementCalls(true); + incrementActiveCalls(); + void * job; + uno_threadpool_enter(tp, &job); + resp.reset(static_cast< IncomingReply * >(job)); + decrementActiveCalls(); + decrementCalls(); + } + if (resp == nullptr) + { + throw css::lang::DisposedException( + "Binary URP bridge disposed during call", + getXWeak()); + } + *returnValue = resp->returnValue; + if (!resp->exception) { + *outArguments = resp->outArguments; + } + return resp->exception; +} + +void Bridge::sendRequestChangeRequest() { + assert(mode_ == MODE_REQUESTED); + random_ = random(); + std::vector< BinaryAny > a; + a.emplace_back( + css::uno::TypeDescription(cppu::UnoType< sal_Int32 >::get()), + &random_); + sendProtPropRequest(OutgoingRequest::KIND_REQUEST_CHANGE, a); +} + +void Bridge::handleRequestChangeReply( + bool exception, BinaryAny const & returnValue) +{ + try { + throwException(exception, returnValue); + } catch (css::uno::RuntimeException & e) { + // Before OOo 2.2, Java URP would throw a RuntimeException when + // receiving a requestChange message (see i#35277 "Java URP: Support + // Manipulation of Protocol Properties"): + if (mode_ != MODE_REQUESTED) { + throw; + } + SAL_WARN( + "binaryurp", + "requestChange caught " << e << " in state 'requested'"); + mode_ = MODE_NORMAL; + getWriter()->unblock(); + decrementCalls(); + return; + } + sal_Int32 n = *static_cast< sal_Int32 * >( + returnValue.getValue( + css::uno::TypeDescription(cppu::UnoType< sal_Int32 >::get()))); + sal_Int32 exp = 0; + switch (mode_) { + case MODE_REQUESTED: + case MODE_REPLY_1: + exp = 1; + break; + case MODE_REPLY_MINUS1: + exp = -1; + mode_ = MODE_REQUESTED; + break; + case MODE_REPLY_0: + exp = 0; + mode_ = MODE_WAIT; + break; + default: + assert(false); // this cannot happen + break; + } + if (n != exp) { + throw css::uno::RuntimeException( + "URP: requestChange reply with unexpected return value received", + getXWeak()); + } + decrementCalls(); + switch (exp) { + case -1: + sendRequestChangeRequest(); + break; + case 0: + break; + case 1: + sendCommitChangeRequest(); + break; + default: + assert(false); // this cannot happen + break; + } +} + +void Bridge::handleCommitChangeReply( + bool exception, BinaryAny const & returnValue) +{ + bool bCcMode = true; + try { + throwException(exception, returnValue); + } catch (const css::bridge::InvalidProtocolChangeException &) { + bCcMode = false; + } + if (bCcMode) { + setCurrentContextMode(); + } + assert(mode_ == MODE_REQUESTED || mode_ == MODE_REPLY_1); + mode_ = MODE_NORMAL; + getWriter()->unblock(); + decrementCalls(); +} + +void Bridge::handleRequestChangeRequest( + rtl::ByteSequence const & tid, std::vector< BinaryAny > const & inArguments) +{ + assert(inArguments.size() == 1); + switch (mode_) { + case MODE_REQUESTED: + { + sal_Int32 n2 = *static_cast< sal_Int32 * >( + inArguments[0].getValue( + css::uno::TypeDescription( + cppu::UnoType< sal_Int32 >::get()))); + sal_Int32 ret; + if (n2 > random_) { + ret = 1; + mode_ = MODE_REPLY_0; + } else if (n2 == random_) { + ret = -1; + mode_ = MODE_REPLY_MINUS1; + } else { + ret = 0; + mode_ = MODE_REPLY_1; + } + getWriter()->sendDirectReply( + tid, protPropRequest_, false, + BinaryAny( + css::uno::TypeDescription( + cppu::UnoType< sal_Int32 >::get()), + &ret), + std::vector< BinaryAny >()); + break; + } + case MODE_NORMAL: + { + mode_ = MODE_NORMAL_WAIT; + sal_Int32 ret = 1; + getWriter()->queueReply( + tid, protPropRequest_, false, false, + BinaryAny( + css::uno::TypeDescription( + cppu::UnoType< sal_Int32 >::get()), + &ret), + std::vector< BinaryAny >(), false); + break; + } + default: + throw css::uno::RuntimeException( + "URP: unexpected requestChange request received", + getXWeak()); + } +} + +void Bridge::handleCommitChangeRequest( + rtl::ByteSequence const & tid, std::vector< BinaryAny > const & inArguments) +{ + bool bCcMode = false; + bool bExc = false; + BinaryAny ret; + assert(inArguments.size() == 1); + css::uno::Sequence< css::bridge::ProtocolProperty > s; + [[maybe_unused]] bool ok = (mapBinaryToCppAny(inArguments[0]) >>= s); + assert(ok); + for (const auto & pp : std::as_const(s)) { + if (pp.Name == "CurrentContext") { + bCcMode = true; + } else { + bCcMode = false; + bExc = true; + ret = mapCppToBinaryAny( + css::uno::Any( + css::bridge::InvalidProtocolChangeException( + "InvalidProtocolChangeException", + css::uno::Reference< css::uno::XInterface >(), pp, + 1))); + break; + } + } + switch (mode_) { + case MODE_WAIT: + getWriter()->sendDirectReply( + tid, protPropCommit_, bExc, ret, std::vector< BinaryAny >()); + if (bCcMode) { + setCurrentContextMode(); + mode_ = MODE_NORMAL; + getWriter()->unblock(); + } else { + mode_ = MODE_REQUESTED; + sendRequestChangeRequest(); + } + break; + case MODE_NORMAL_WAIT: + getWriter()->queueReply( + tid, protPropCommit_, false, false, ret, std::vector< BinaryAny >(), + bCcMode); + mode_ = MODE_NORMAL; + break; + default: + throw css::uno::RuntimeException( + "URP: unexpected commitChange request received", + getXWeak()); + } +} + +OutgoingRequest Bridge::lastOutgoingRequest(rtl::ByteSequence const & tid) { + OutgoingRequest req(outgoingRequests_.top(tid)); + outgoingRequests_.pop(tid); + return req; +} + +bool Bridge::isProtocolPropertiesRequest( + std::u16string_view oid, css::uno::TypeDescription const & type) const +{ + return oid == protPropOid_ && type.equals(protPropType_); +} + +void Bridge::setCurrentContextMode() { + std::lock_guard g(mutex_); + currentContextMode_ = true; +} + +bool Bridge::isCurrentContextMode() { + std::lock_guard g(mutex_); + return currentContextMode_; +} + +Bridge::~Bridge() { +#if OSL_DEBUG_LEVEL > 0 + { + std::lock_guard g(mutex_); + SAL_WARN_IF( + state_ == STATE_STARTED || state_ == STATE_TERMINATED, "binaryurp", + "undisposed bridge \"" << name_ <<"\" in state " << state_ + << ", potential deadlock ahead"); + } +#endif + dispose(); +} + +css::uno::Reference< css::uno::XInterface > Bridge::getInstance( + OUString const & sInstanceName) +{ + if (sInstanceName.isEmpty()) { + throw css::uno::RuntimeException( + "XBridge::getInstance sInstanceName must be non-empty", + getXWeak()); + } + for (sal_Int32 i = 0; i != sInstanceName.getLength(); ++i) { + if (sInstanceName[i] > 0x7F) { + throw css::uno::RuntimeException( + "XBridge::getInstance sInstanceName contains non-ASCII" + " character"); + } + } + css::uno::TypeDescription ifc(cppu::UnoType<css::uno::XInterface>::get()); + typelib_TypeDescription * p = ifc.get(); + std::vector< BinaryAny > inArgs; + inArgs.emplace_back( + css::uno::TypeDescription(cppu::UnoType< css::uno::Type >::get()), + &p); + BinaryAny ret; + std::vector< BinaryAny> outArgs; + bool bExc = makeCall( + sInstanceName, + css::uno::TypeDescription( + "com.sun.star.uno.XInterface::queryInterface"), + false, std::move(inArgs), &ret, &outArgs); + throwException(bExc, ret); + auto const t = ret.getType(); + if (t.get()->eTypeClass == typelib_TypeClass_VOID) { + return {}; + } + if (!t.equals(ifc)) { + throw css::uno::RuntimeException( + "initial object queryInterface for OID \"" + sInstanceName + "\" returned ANY of type " + + OUString::unacquired(&t.get()->pTypeName)); + } + auto const val = *static_cast< uno_Interface ** >(ret.getValue(ifc)); + if (val == nullptr) { + throw css::uno::RuntimeException( + "initial object queryInterface for OID \"" + sInstanceName + + "\" returned null css.uno.XInterface ANY"); + } + return css::uno::Reference< css::uno::XInterface >( + static_cast< css::uno::XInterface * >( + binaryToCppMapping_.mapInterface( + val, + ifc.get())), + SAL_NO_ACQUIRE); +} + +OUString Bridge::getName() { + return name_; +} + +OUString Bridge::getDescription() { + OUString b = name_ + ":" + connection_->getDescription(); + return b; +} + +void Bridge::dispose() { + // For terminate(true) not to deadlock, an external protocol must ensure + // that dispose is not called from a thread pool worker thread (that dispose + // is never called from the reader or writer thread is already ensured + // internally): + terminate(true); + // OOo expects dispose to not return while there are still remote calls in + // progress; an external protocol must ensure that dispose is not called + // from within an incoming or outgoing remote call, as passive_.wait() would + // otherwise deadlock: + passive_.wait(); +} + +void Bridge::addEventListener( + css::uno::Reference< css::lang::XEventListener > const & xListener) +{ + assert(xListener.is()); + { + std::lock_guard g(mutex_); + assert(state_ != STATE_INITIAL); + if (state_ == STATE_STARTED) { + listeners_.push_back(xListener); + return; + } + } + xListener->disposing( + css::lang::EventObject(getXWeak())); +} + +void Bridge::removeEventListener( + css::uno::Reference< css::lang::XEventListener > const & aListener) +{ + std::lock_guard g(mutex_); + Listeners::iterator i( + std::find(listeners_.begin(), listeners_.end(), aListener)); + if (i != listeners_.end()) { + listeners_.erase(i); + } +} + +void Bridge::sendCommitChangeRequest() { + assert(mode_ == MODE_REQUESTED || mode_ == MODE_REPLY_1); + css::uno::Sequence< css::bridge::ProtocolProperty > s(1); + s.getArray()[0].Name = "CurrentContext"; + std::vector< BinaryAny > a { mapCppToBinaryAny(css::uno::Any(s)) }; + sendProtPropRequest(OutgoingRequest::KIND_COMMIT_CHANGE, a); +} + +void Bridge::sendProtPropRequest( + OutgoingRequest::Kind kind, std::vector< BinaryAny > const & inArguments) +{ + assert( + kind == OutgoingRequest::KIND_REQUEST_CHANGE || + kind == OutgoingRequest::KIND_COMMIT_CHANGE); + incrementCalls(false); + css::uno::TypeDescription member( + kind == OutgoingRequest::KIND_REQUEST_CHANGE + ? protPropRequest_ : protPropCommit_); + PopOutgoingRequest pop( + outgoingRequests_, protPropTid_, OutgoingRequest(kind, member, false)); + getWriter()->sendDirectRequest( + protPropTid_, protPropOid_, protPropType_, member, inArguments); + pop.clear(); +} + +void Bridge::makeReleaseCall( + OUString const & oid, css::uno::TypeDescription const & type) +{ + //HACK to decouple the processing of release calls from all other threads. Normally, sending + // the release request should use the current thread's TID (via AttachThread), which would cause + // that asynchronous request to be processed by a physical thread that is paired with the + // physical thread processing the normal synchronous call stack (see ThreadIdHashMap in + // cppu/source/threadpool/threadpool.hxx). However, that can lead to deadlock when a thread + // illegally makes a synchronous UNO call with the SolarMutex locked (e.g., + // SfxBaseModel::postEvent_Impl in sfx2/source/doc/sfxbasemodel.cxx doing documentEventOccurred + // and notifyEvent calls), and while that call is on the stack the remote side sends back some + // release request on the same logical UNO thread for an object that wants to acquire the + // SolarMutex in its destructor (e.g., SwXTextDocument in sw/inc/unotxdoc.hxx holding its + // m_pImpl via an sw::UnoImplPtr). While the correct approach would be to not make UNO calls + // with the SolarMutex (or any other mutex) locked, fixing that would probably be a heroic + // effort. So for now live with this hack, hoping that it does not introduce any new issues of + // its own: + static auto const tid = [] { + static sal_Int8 const id[] = {'r', 'e', 'l', 'e', 'a', 's', 'e', 'h', 'a', 'c', 'k'}; + return rtl::ByteSequence(id, std::size(id)); + }(); + sendRequest( + tid, oid, type, + css::uno::TypeDescription("com.sun.star.uno.XInterface::release"), + std::vector< BinaryAny >()); +} + +void Bridge::sendRequest( + rtl::ByteSequence const & tid, OUString const & oid, + css::uno::TypeDescription const & type, + css::uno::TypeDescription const & member, + std::vector< BinaryAny >&& inArguments) +{ + getWriter()->queueRequest(tid, oid, type, member, std::move(inArguments)); +} + +void Bridge::throwException(bool exception, BinaryAny const & value) { + if (exception) { + cppu::throwException(mapBinaryToCppAny(value)); + } +} + +css::uno::Any Bridge::mapBinaryToCppAny(BinaryAny const & binaryAny) { + BinaryAny in(binaryAny); + css::uno::Any out; + out.~Any(); + uno_copyAndConvertData( + &out, &in.get(), + css::uno::TypeDescription(cppu::UnoType< css::uno::Any >::get()).get(), + binaryToCppMapping_.get()); + return out; +} + +bool Bridge::becameUnused() const { + return stubs_.empty() && proxies_ == 0 && calls_ == 0 && normalCall_; +} + +void Bridge::terminateWhenUnused(bool unused) { + if (unused) { + // That the current thread considers the bridge unused implies that it + // is not within an incoming or outgoing remote call (so calling + // terminate cannot lead to deadlock): + terminate(false); + } +} + +void Bridge::checkDisposed() { + assert(state_ != STATE_INITIAL); + if (state_ != STATE_STARTED) { + throw css::lang::DisposedException( + "Binary URP bridge already disposed", + getXWeak()); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |