summaryrefslogtreecommitdiffstats
path: root/ipc/glue/MessageLink.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ipc/glue/MessageLink.cpp')
-rw-r--r--ipc/glue/MessageLink.cpp387
1 files changed, 387 insertions, 0 deletions
diff --git a/ipc/glue/MessageLink.cpp b/ipc/glue/MessageLink.cpp
new file mode 100644
index 000000000..6a1bda02d
--- /dev/null
+++ b/ipc/glue/MessageLink.cpp
@@ -0,0 +1,387 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sw=4 ts=4 et :
+ */
+/* 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/. */
+
+#include "mozilla/ipc/MessageLink.h"
+#include "mozilla/ipc/MessageChannel.h"
+#include "mozilla/ipc/BrowserProcessSubThread.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "chrome/common/ipc_channel.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "nsDebug.h"
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
+#include "nsISupportsImpl.h"
+#include "nsXULAppAPI.h"
+
+using namespace mozilla;
+using namespace std;
+
+// We rely on invariants about the lifetime of the transport:
+//
+// - outlives this MessageChannel
+// - deleted on the IO thread
+//
+// These invariants allow us to send messages directly through the
+// transport without having to worry about orphaned Send() tasks on
+// the IO thread touching MessageChannel memory after it's been deleted
+// on the worker thread. We also don't need to refcount the
+// Transport, because whatever task triggers its deletion only runs on
+// the IO thread, and only runs after this MessageChannel is done with
+// the Transport.
+
+namespace mozilla {
+namespace ipc {
+
+MessageLink::MessageLink(MessageChannel *aChan)
+ : mChan(aChan)
+{
+}
+
+MessageLink::~MessageLink()
+{
+#ifdef DEBUG
+ mChan = nullptr;
+#endif
+}
+
+ProcessLink::ProcessLink(MessageChannel *aChan)
+ : MessageLink(aChan)
+ , mTransport(nullptr)
+ , mIOLoop(nullptr)
+ , mExistingListener(nullptr)
+{
+}
+
+ProcessLink::~ProcessLink()
+{
+#ifdef DEBUG
+ mTransport = nullptr;
+ mIOLoop = nullptr;
+ mExistingListener = nullptr;
+#endif
+}
+
+void
+ProcessLink::Open(mozilla::ipc::Transport* aTransport, MessageLoop *aIOLoop, Side aSide)
+{
+ NS_PRECONDITION(aTransport, "need transport layer");
+
+ // FIXME need to check for valid channel
+
+ mTransport = aTransport;
+
+ // FIXME figure out whether we're in parent or child, grab IO loop
+ // appropriately
+ bool needOpen = true;
+ if(aIOLoop) {
+ // We're a child or using the new arguments. Either way, we
+ // need an open.
+ needOpen = true;
+ mChan->mSide = (aSide == UnknownSide) ? ChildSide : aSide;
+ } else {
+ NS_PRECONDITION(aSide == UnknownSide, "expected default side arg");
+
+ // parent
+ mChan->mSide = ParentSide;
+ needOpen = false;
+ aIOLoop = XRE_GetIOMessageLoop();
+ }
+
+ mIOLoop = aIOLoop;
+
+ NS_ASSERTION(mIOLoop, "need an IO loop");
+ NS_ASSERTION(mChan->mWorkerLoop, "need a worker loop");
+
+ {
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ if (needOpen) {
+ // Transport::Connect() has not been called. Call it so
+ // we start polling our pipe and processing outgoing
+ // messages.
+ mIOLoop->PostTask(NewNonOwningRunnableMethod(this, &ProcessLink::OnChannelOpened));
+ } else {
+ // Transport::Connect() has already been called. Take
+ // over the channel from the previous listener and process
+ // any queued messages.
+ mIOLoop->PostTask(NewNonOwningRunnableMethod(this, &ProcessLink::OnTakeConnectedChannel));
+ }
+
+ // Should not wait here if something goes wrong with the channel.
+ while (!mChan->Connected() && mChan->mChannelState != ChannelError) {
+ mChan->mMonitor->Wait();
+ }
+ }
+}
+
+void
+ProcessLink::EchoMessage(Message *msg)
+{
+ mChan->AssertWorkerThread();
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ mIOLoop->PostTask(NewNonOwningRunnableMethod<Message*>(this, &ProcessLink::OnEchoMessage, msg));
+ // OnEchoMessage takes ownership of |msg|
+}
+
+void
+ProcessLink::SendMessage(Message *msg)
+{
+ if (msg->size() > IPC::Channel::kMaximumMessageSize) {
+#ifdef MOZ_CRASHREPORTER
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCMessageName"), nsDependentCString(msg->name()));
+ CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("IPCMessageSize"), nsPrintfCString("%d", msg->size()));
+#endif
+ MOZ_CRASH("IPC message size is too large");
+ }
+
+ mChan->AssertWorkerThread();
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ mIOLoop->PostTask(NewNonOwningRunnableMethod<Message*>(mTransport, &Transport::Send, msg));
+}
+
+void
+ProcessLink::SendClose()
+{
+ mChan->AssertWorkerThread();
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ mIOLoop->PostTask(NewNonOwningRunnableMethod(this, &ProcessLink::OnCloseChannel));
+}
+
+ThreadLink::ThreadLink(MessageChannel *aChan, MessageChannel *aTargetChan)
+ : MessageLink(aChan),
+ mTargetChan(aTargetChan)
+{
+}
+
+ThreadLink::~ThreadLink()
+{
+ MOZ_ASSERT(mChan);
+ MOZ_ASSERT(mChan->mMonitor);
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ // Bug 848949: We need to prevent the other side
+ // from sending us any more messages to avoid Use-After-Free.
+ // The setup here is as shown:
+ //
+ // (Us) (Them)
+ // MessageChannel MessageChannel
+ // | ^ \ / ^ |
+ // | | X | |
+ // v | / \ | v
+ // ThreadLink ThreadLink
+ //
+ // We want to null out the diagonal link from their ThreadLink
+ // to our MessageChannel. Note that we must hold the monitor so
+ // that we do this atomically with respect to them trying to send
+ // us a message. Since the channels share the same monitor this
+ // also protects against the two ~ThreadLink() calls racing.
+ if (mTargetChan) {
+ MOZ_ASSERT(mTargetChan->mLink);
+ static_cast<ThreadLink*>(mTargetChan->mLink)->mTargetChan = nullptr;
+ }
+ mTargetChan = nullptr;
+}
+
+void
+ThreadLink::EchoMessage(Message *msg)
+{
+ mChan->AssertWorkerThread();
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ mChan->OnMessageReceivedFromLink(Move(*msg));
+ delete msg;
+}
+
+void
+ThreadLink::SendMessage(Message *msg)
+{
+ mChan->AssertWorkerThread();
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ if (mTargetChan)
+ mTargetChan->OnMessageReceivedFromLink(Move(*msg));
+ delete msg;
+}
+
+void
+ThreadLink::SendClose()
+{
+ mChan->AssertWorkerThread();
+ mChan->mMonitor->AssertCurrentThreadOwns();
+
+ mChan->mChannelState = ChannelClosed;
+
+ // In a ProcessLink, we would close our half the channel. This
+ // would show up on the other side as an error on the I/O thread.
+ // The I/O thread would then invoke OnChannelErrorFromLink().
+ // As usual, we skip that process and just invoke the
+ // OnChannelErrorFromLink() method directly.
+ if (mTargetChan)
+ mTargetChan->OnChannelErrorFromLink();
+}
+
+bool
+ThreadLink::Unsound_IsClosed() const
+{
+ MonitorAutoLock lock(*mChan->mMonitor);
+ return mChan->mChannelState == ChannelClosed;
+}
+
+uint32_t
+ThreadLink::Unsound_NumQueuedMessages() const
+{
+ // ThreadLinks don't have a message queue.
+ return 0;
+}
+
+//
+// The methods below run in the context of the IO thread
+//
+
+void
+ProcessLink::OnMessageReceived(Message&& msg)
+{
+ AssertIOThread();
+ NS_ASSERTION(mChan->mChannelState != ChannelError, "Shouldn't get here!");
+ MonitorAutoLock lock(*mChan->mMonitor);
+ mChan->OnMessageReceivedFromLink(Move(msg));
+}
+
+void
+ProcessLink::OnEchoMessage(Message* msg)
+{
+ AssertIOThread();
+ OnMessageReceived(Move(*msg));
+ delete msg;
+}
+
+void
+ProcessLink::OnChannelOpened()
+{
+ AssertIOThread();
+
+ {
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ mExistingListener = mTransport->set_listener(this);
+#ifdef DEBUG
+ if (mExistingListener) {
+ queue<Message> pending;
+ mExistingListener->GetQueuedMessages(pending);
+ MOZ_ASSERT(pending.empty());
+ }
+#endif // DEBUG
+
+ mChan->mChannelState = ChannelOpening;
+ lock.Notify();
+ }
+ /*assert*/mTransport->Connect();
+}
+
+void
+ProcessLink::OnTakeConnectedChannel()
+{
+ AssertIOThread();
+
+ queue<Message> pending;
+ {
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ mChan->mChannelState = ChannelConnected;
+
+ mExistingListener = mTransport->set_listener(this);
+ if (mExistingListener) {
+ mExistingListener->GetQueuedMessages(pending);
+ }
+ lock.Notify();
+ }
+
+ // Dispatch whatever messages the previous listener had queued up.
+ while (!pending.empty()) {
+ OnMessageReceived(Move(pending.front()));
+ pending.pop();
+ }
+}
+
+void
+ProcessLink::OnChannelConnected(int32_t peer_pid)
+{
+ AssertIOThread();
+
+ bool notifyChannel = false;
+
+ {
+ MonitorAutoLock lock(*mChan->mMonitor);
+ // Only update channel state if its still thinks its opening. Do not
+ // force it into connected if it has errored out, started closing, etc.
+ if (mChan->mChannelState == ChannelOpening) {
+ mChan->mChannelState = ChannelConnected;
+ mChan->mMonitor->Notify();
+ notifyChannel = true;
+ }
+ }
+
+ if (mExistingListener)
+ mExistingListener->OnChannelConnected(peer_pid);
+
+ if (notifyChannel) {
+ mChan->OnChannelConnected(peer_pid);
+ }
+}
+
+void
+ProcessLink::OnChannelError()
+{
+ AssertIOThread();
+
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ MOZ_ALWAYS_TRUE(this == mTransport->set_listener(mExistingListener));
+
+ mChan->OnChannelErrorFromLink();
+}
+
+void
+ProcessLink::OnCloseChannel()
+{
+ AssertIOThread();
+
+ mTransport->Close();
+
+ MonitorAutoLock lock(*mChan->mMonitor);
+
+ DebugOnly<IPC::Channel::Listener*> previousListener =
+ mTransport->set_listener(mExistingListener);
+
+ // OnChannelError may have reset the listener already.
+ MOZ_ASSERT(previousListener == this ||
+ previousListener == mExistingListener);
+
+ mChan->mChannelState = ChannelClosed;
+ mChan->mMonitor->Notify();
+}
+
+bool
+ProcessLink::Unsound_IsClosed() const
+{
+ return mTransport->Unsound_IsClosed();
+}
+
+uint32_t
+ProcessLink::Unsound_NumQueuedMessages() const
+{
+ return mTransport->Unsound_NumQueuedMessages();
+}
+
+} // namespace ipc
+} // namespace mozilla