summaryrefslogtreecommitdiffstats
path: root/ipc/glue/ProtocolUtils.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /ipc/glue/ProtocolUtils.cpp
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ipc/glue/ProtocolUtils.cpp')
-rw-r--r--ipc/glue/ProtocolUtils.cpp835
1 files changed, 835 insertions, 0 deletions
diff --git a/ipc/glue/ProtocolUtils.cpp b/ipc/glue/ProtocolUtils.cpp
new file mode 100644
index 0000000000..51fb4d7e56
--- /dev/null
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -0,0 +1,835 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "base/process_util.h"
+#include "base/task.h"
+
+#ifdef OS_POSIX
+# include <errno.h>
+#endif
+#include <type_traits>
+
+#include "mozilla/IntegerPrintfMacros.h"
+
+#include "mozilla/ipc/ProtocolMessageUtils.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+
+#include "mozilla/ipc/MessageChannel.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "mozilla/StaticMutex.h"
+#if defined(DEBUG) || defined(FUZZING)
+# include "mozilla/Tokenizer.h"
+#endif
+#include "mozilla/Unused.h"
+#include "nsPrintfCString.h"
+
+#if defined(MOZ_SANDBOX) && defined(XP_WIN)
+# include "mozilla/sandboxTarget.h"
+#endif
+
+#if defined(XP_WIN)
+# include "aclapi.h"
+# include "sddl.h"
+#endif
+
+#ifdef FUZZING_SNAPSHOT
+# include "mozilla/fuzzing/IPCFuzzController.h"
+#endif
+
+using namespace IPC;
+
+using base::GetCurrentProcId;
+using base::ProcessHandle;
+using base::ProcessId;
+
+namespace mozilla {
+
+#if defined(XP_WIN)
+// Generate RAII classes for LPTSTR and PSECURITY_DESCRIPTOR.
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedLPTStr,
+ std::remove_pointer_t<LPTSTR>,
+ ::LocalFree)
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(
+ ScopedPSecurityDescriptor, std::remove_pointer_t<PSECURITY_DESCRIPTOR>,
+ ::LocalFree)
+#endif
+
+namespace ipc {
+
+IPCResult IPCResult::Fail(NotNull<IProtocol*> actor, const char* where,
+ const char* why) {
+ // Calls top-level protocol to handle the error.
+ nsPrintfCString errorMsg("%s %s\n", where, why);
+ actor->GetIPCChannel()->Listener()->ProcessingError(
+ HasResultCodes::MsgProcessingError, errorMsg.get());
+
+ MOZ_ASSERT_UNLESS_FUZZING(false,
+ "Please ensure to IPC_FAIL only when in an "
+ "unrecoverable, unexpected state.");
+
+ return IPCResult(false);
+}
+
+void AnnotateSystemError() {
+ int64_t error = 0;
+#if defined(XP_WIN)
+ error = ::GetLastError();
+#elif defined(OS_POSIX)
+ error = errno;
+#endif
+ if (error) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCSystemError,
+ nsPrintfCString("%" PRId64, error));
+ }
+}
+
+#if defined(XP_MACOSX)
+void AnnotateCrashReportWithErrno(CrashReporter::Annotation tag, int error) {
+ CrashReporter::AnnotateCrashReport(tag, error);
+}
+#endif // defined(XP_MACOSX)
+
+#if defined(DEBUG) || defined(FUZZING)
+// This overload is for testability; application code should use the single-
+// argument version (defined in the ProtocolUtils.h) which takes the filter from
+// the environment.
+bool LoggingEnabledFor(const char* aTopLevelProtocol, const char* aFilter) {
+ if (!aFilter) {
+ return false;
+ }
+ if (strcmp(aFilter, "1") == 0) {
+ return true;
+ }
+
+ const char kDelimiters[] = ", ";
+ Tokenizer tokens(aFilter, kDelimiters);
+ Tokenizer::Token t;
+ while (tokens.Next(t)) {
+ if (t.Type() == Tokenizer::TOKEN_WORD &&
+ t.AsString() == aTopLevelProtocol) {
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif // defined(DEBUG) || defined(FUZZING)
+
+void LogMessageForProtocol(const char* aTopLevelProtocol,
+ base::ProcessId aOtherPid,
+ const char* aContextDescription, uint32_t aMessageId,
+ MessageDirection aDirection) {
+ nsPrintfCString logMessage(
+ "[time: %" PRId64 "][%" PRIPID "%s%" PRIPID "] [%s] %s %s\n", PR_Now(),
+ base::GetCurrentProcId(),
+ aDirection == MessageDirection::eReceiving ? "<-" : "->", aOtherPid,
+ aTopLevelProtocol, aContextDescription,
+ StringFromIPCMessageType(aMessageId));
+#ifdef ANDROID
+ __android_log_write(ANDROID_LOG_INFO, "GeckoIPC", logMessage.get());
+#endif
+ fputs(logMessage.get(), stderr);
+}
+
+void ProtocolErrorBreakpoint(const char* aMsg) {
+ // Bugs that generate these error messages can be tough to
+ // reproduce. Log always in the hope that someone finds the error
+ // message.
+ printf_stderr("IPDL protocol error: %s\n", aMsg);
+}
+
+void PickleFatalError(const char* aMsg, IProtocol* aActor) {
+ if (aActor) {
+ aActor->FatalError(aMsg);
+ } else {
+ FatalError(aMsg, false);
+ }
+}
+
+void FatalError(const char* aMsg, bool aIsParent) {
+#ifndef FUZZING
+ ProtocolErrorBreakpoint(aMsg);
+#endif
+
+ nsAutoCString formattedMessage("IPDL error: \"");
+ formattedMessage.AppendASCII(aMsg);
+ if (aIsParent) {
+ // We're going to crash the parent process because at this time
+ // there's no other really nice way of getting a minidump out of
+ // this process if we're off the main thread.
+ formattedMessage.AppendLiteral("\". Intentionally crashing.");
+ NS_ERROR(formattedMessage.get());
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::IPCFatalErrorMsg, nsDependentCString(aMsg));
+ AnnotateSystemError();
+#ifndef FUZZING
+ MOZ_CRASH("IPC FatalError in the parent process!");
+#endif
+ } else {
+ formattedMessage.AppendLiteral("\". abort()ing as a result.");
+#ifndef FUZZING
+ MOZ_CRASH_UNSAFE(formattedMessage.get());
+#endif
+ }
+}
+
+void LogicError(const char* aMsg) { MOZ_CRASH_UNSAFE(aMsg); }
+
+void ActorIdReadError(const char* aActorDescription) {
+#ifndef FUZZING
+ MOZ_CRASH_UNSAFE_PRINTF("Error deserializing id for %s", aActorDescription);
+#endif
+}
+
+void BadActorIdError(const char* aActorDescription) {
+ nsPrintfCString message("bad id for %s", aActorDescription);
+ ProtocolErrorBreakpoint(message.get());
+}
+
+void ActorLookupError(const char* aActorDescription) {
+ nsPrintfCString message("could not lookup id for %s", aActorDescription);
+ ProtocolErrorBreakpoint(message.get());
+}
+
+void MismatchedActorTypeError(const char* aActorDescription) {
+ nsPrintfCString message("actor that should be of type %s has different type",
+ aActorDescription);
+ ProtocolErrorBreakpoint(message.get());
+}
+
+void UnionTypeReadError(const char* aUnionName) {
+ MOZ_CRASH_UNSAFE_PRINTF("error deserializing type of union %s", aUnionName);
+}
+
+void ArrayLengthReadError(const char* aElementName) {
+ MOZ_CRASH_UNSAFE_PRINTF("error deserializing length of %s[]", aElementName);
+}
+
+void SentinelReadError(const char* aClassName) {
+ MOZ_CRASH_UNSAFE_PRINTF("incorrect sentinel when reading %s", aClassName);
+}
+
+ActorLifecycleProxy::ActorLifecycleProxy(IProtocol* aActor) : mActor(aActor) {
+ MOZ_ASSERT(mActor);
+ MOZ_ASSERT(mActor->CanSend(),
+ "Cannot create LifecycleProxy for non-connected actor!");
+
+ // Take a reference to our manager's lifecycle proxy to try to hold it &
+ // ensure it doesn't die before us.
+ if (mActor->mManager) {
+ mManager = mActor->mManager->mLifecycleProxy;
+ }
+
+ // Record that we've taken our first reference to our actor.
+ mActor->ActorAlloc();
+}
+
+WeakActorLifecycleProxy* ActorLifecycleProxy::GetWeakProxy() {
+ if (!mWeakProxy) {
+ mWeakProxy = new WeakActorLifecycleProxy(this);
+ }
+ return mWeakProxy;
+}
+
+ActorLifecycleProxy::~ActorLifecycleProxy() {
+ if (mWeakProxy) {
+ mWeakProxy->mProxy = nullptr;
+ mWeakProxy = nullptr;
+ }
+
+ // When the LifecycleProxy's lifetime has come to an end, it means that the
+ // actor should have its `Dealloc` method called on it. In a well-behaved
+ // actor, this will release the IPC-held reference to the actor.
+ //
+ // If the actor has already died before the `LifecycleProxy`, the `IProtocol`
+ // destructor below will clear our reference to it, preventing us from
+ // performing a use-after-free here.
+ if (!mActor) {
+ return;
+ }
+
+ // Clear our actor's state back to inactive, and then invoke ActorDealloc.
+ MOZ_ASSERT(mActor->mLinkStatus == LinkStatus::Destroyed,
+ "Deallocating non-destroyed actor!");
+ mActor->mLifecycleProxy = nullptr;
+ mActor->mLinkStatus = LinkStatus::Inactive;
+ mActor->ActorDealloc();
+ mActor = nullptr;
+}
+
+WeakActorLifecycleProxy::WeakActorLifecycleProxy(ActorLifecycleProxy* aProxy)
+ : mProxy(aProxy), mActorEventTarget(GetCurrentSerialEventTarget()) {}
+
+WeakActorLifecycleProxy::~WeakActorLifecycleProxy() {
+ MOZ_DIAGNOSTIC_ASSERT(!mProxy, "Destroyed before mProxy was cleared?");
+}
+
+IProtocol* WeakActorLifecycleProxy::Get() const {
+ MOZ_DIAGNOSTIC_ASSERT(mActorEventTarget->IsOnCurrentThread());
+ return mProxy ? mProxy->Get() : nullptr;
+}
+
+WeakActorLifecycleProxy* IProtocol::GetWeakLifecycleProxy() {
+ return mLifecycleProxy ? mLifecycleProxy->GetWeakProxy() : nullptr;
+}
+
+IProtocol::~IProtocol() {
+ // If the actor still has a lifecycle proxy when it is being torn down, it
+ // means that IPC was not given control over the lifecycle of the actor
+ // correctly. Usually this means that the actor was destroyed while IPC is
+ // calling a message handler for it, and the actor incorrectly frees itself
+ // during that operation.
+ //
+ // As this happens unfortunately frequently, due to many odd protocols in
+ // Gecko, simply emit a warning and clear the weak backreference from our
+ // LifecycleProxy back to us.
+ if (mLifecycleProxy) {
+ NS_WARNING(
+ nsPrintfCString("Actor destructor for '%s%s' called before IPC "
+ "lifecycle complete!\n"
+ "References to this actor may unexpectedly dangle!",
+ GetProtocolName(),
+ GetSide() == ChildSide ? "Child" : "Parent")
+ .get());
+
+ mLifecycleProxy->mActor = nullptr;
+
+ // If we are somehow being destroyed while active, make sure that the
+ // existing IPC reference has been freed. If the status of the actor is
+ // `Destroyed`, the reference has already been freed, and we shouldn't free
+ // it a second time.
+ MOZ_ASSERT(mLinkStatus != LinkStatus::Inactive);
+ if (mLinkStatus != LinkStatus::Destroyed) {
+ NS_IF_RELEASE(mLifecycleProxy);
+ }
+ mLifecycleProxy = nullptr;
+ }
+}
+
+// The following methods either directly forward to the toplevel protocol, or
+// almost directly do.
+int32_t IProtocol::Register(IProtocol* aRouted) {
+ return mToplevel->Register(aRouted);
+}
+int32_t IProtocol::RegisterID(IProtocol* aRouted, int32_t aId) {
+ return mToplevel->RegisterID(aRouted, aId);
+}
+IProtocol* IProtocol::Lookup(int32_t aId) { return mToplevel->Lookup(aId); }
+void IProtocol::Unregister(int32_t aId) {
+ if (aId == mId) {
+ mId = kFreedActorId;
+ }
+ return mToplevel->Unregister(aId);
+}
+
+Shmem::SharedMemory* IProtocol::CreateSharedMemory(size_t aSize, bool aUnsafe,
+ int32_t* aId) {
+ return mToplevel->CreateSharedMemory(aSize, aUnsafe, aId);
+}
+Shmem::SharedMemory* IProtocol::LookupSharedMemory(int32_t aId) {
+ return mToplevel->LookupSharedMemory(aId);
+}
+bool IProtocol::IsTrackingSharedMemory(Shmem::SharedMemory* aSegment) {
+ return mToplevel->IsTrackingSharedMemory(aSegment);
+}
+bool IProtocol::DestroySharedMemory(Shmem& aShmem) {
+ return mToplevel->DestroySharedMemory(aShmem);
+}
+
+MessageChannel* IProtocol::GetIPCChannel() {
+ return mToplevel->GetIPCChannel();
+}
+const MessageChannel* IProtocol::GetIPCChannel() const {
+ return mToplevel->GetIPCChannel();
+}
+
+nsISerialEventTarget* IProtocol::GetActorEventTarget() {
+ return GetIPCChannel()->GetWorkerEventTarget();
+}
+
+void IProtocol::SetId(int32_t aId) {
+ MOZ_ASSERT(mId == aId || mLinkStatus == LinkStatus::Inactive);
+ mId = aId;
+}
+
+Maybe<IProtocol*> IProtocol::ReadActor(IPC::MessageReader* aReader,
+ bool aNullable,
+ const char* aActorDescription,
+ int32_t aProtocolTypeId) {
+ int32_t id;
+ if (!IPC::ReadParam(aReader, &id)) {
+ ActorIdReadError(aActorDescription);
+ return Nothing();
+ }
+
+ if (id == 1 || (id == 0 && !aNullable)) {
+ BadActorIdError(aActorDescription);
+ return Nothing();
+ }
+
+ if (id == 0) {
+ return Some(static_cast<IProtocol*>(nullptr));
+ }
+
+ IProtocol* listener = this->Lookup(id);
+ if (!listener) {
+ ActorLookupError(aActorDescription);
+ return Nothing();
+ }
+
+ if (listener->GetProtocolId() != aProtocolTypeId) {
+ MismatchedActorTypeError(aActorDescription);
+ return Nothing();
+ }
+
+ return Some(listener);
+}
+
+void IProtocol::FatalError(const char* const aErrorMsg) {
+ HandleFatalError(aErrorMsg);
+}
+
+void IProtocol::HandleFatalError(const char* aErrorMsg) {
+ if (IProtocol* manager = Manager()) {
+ manager->HandleFatalError(aErrorMsg);
+ return;
+ }
+
+ mozilla::ipc::FatalError(aErrorMsg, mSide == ParentSide);
+ if (CanSend()) {
+ GetIPCChannel()->CloseWithError();
+ }
+}
+
+bool IProtocol::AllocShmem(size_t aSize, Shmem* aOutMem) {
+ if (!CanSend()) {
+ NS_WARNING(
+ "Shmem not allocated. Cannot communicate with the other actor.");
+ return false;
+ }
+
+ Shmem::id_t id;
+ Shmem::SharedMemory* rawmem(CreateSharedMemory(aSize, false, &id));
+ if (!rawmem) {
+ return false;
+ }
+
+ *aOutMem = Shmem(rawmem, id);
+ return true;
+}
+
+bool IProtocol::AllocUnsafeShmem(size_t aSize, Shmem* aOutMem) {
+ if (!CanSend()) {
+ NS_WARNING(
+ "Shmem not allocated. Cannot communicate with the other actor.");
+ return false;
+ }
+
+ Shmem::id_t id;
+ Shmem::SharedMemory* rawmem(CreateSharedMemory(aSize, true, &id));
+ if (!rawmem) {
+ return false;
+ }
+
+ *aOutMem = Shmem(rawmem, id);
+ return true;
+}
+
+bool IProtocol::DeallocShmem(Shmem& aMem) {
+ bool ok = DestroySharedMemory(aMem);
+#ifdef DEBUG
+ if (!ok) {
+ if (mSide == ChildSide) {
+ FatalError("bad Shmem");
+ } else {
+ NS_WARNING("bad Shmem");
+ }
+ return false;
+ }
+#endif // DEBUG
+ aMem.forget();
+ return ok;
+}
+
+void IProtocol::SetManager(IProtocol* aManager) {
+ MOZ_RELEASE_ASSERT(!mManager || mManager == aManager);
+ mManager = aManager;
+ mToplevel = aManager->mToplevel;
+}
+
+void IProtocol::SetManagerAndRegister(IProtocol* aManager) {
+ // Set the manager prior to registering so registering properly inherits
+ // the manager's event target.
+ SetManager(aManager);
+
+ aManager->Register(this);
+}
+
+void IProtocol::SetManagerAndRegister(IProtocol* aManager, int32_t aId) {
+ // Set the manager prior to registering so registering properly inherits
+ // the manager's event target.
+ SetManager(aManager);
+
+ aManager->RegisterID(this, aId);
+}
+
+bool IProtocol::ChannelSend(UniquePtr<IPC::Message> aMsg) {
+ if (CanSend()) {
+ // NOTE: This send call failing can only occur during toplevel channel
+ // teardown. As this is an async call, this isn't reasonable to predict or
+ // respond to, so just drop the message on the floor silently.
+ GetIPCChannel()->Send(std::move(aMsg));
+ return true;
+ }
+
+ WarnMessageDiscarded(aMsg.get());
+ return false;
+}
+
+bool IProtocol::ChannelSend(UniquePtr<IPC::Message> aMsg,
+ UniquePtr<IPC::Message>* aReply) {
+ if (CanSend()) {
+ return GetIPCChannel()->Send(std::move(aMsg), aReply);
+ }
+
+ WarnMessageDiscarded(aMsg.get());
+ return false;
+}
+
+#ifdef DEBUG
+void IProtocol::WarnMessageDiscarded(IPC::Message* aMsg) {
+ NS_WARNING(nsPrintfCString("IPC message '%s' discarded: actor cannot send",
+ aMsg->name())
+ .get());
+}
+#endif
+
+void IProtocol::ActorConnected() {
+ if (mLinkStatus != LinkStatus::Inactive) {
+ return;
+ }
+
+#ifdef FUZZING_SNAPSHOT
+ fuzzing::IPCFuzzController::instance().OnActorConnected(this);
+#endif
+
+ mLinkStatus = LinkStatus::Connected;
+
+ MOZ_ASSERT(!mLifecycleProxy, "double-connecting live actor");
+ mLifecycleProxy = new ActorLifecycleProxy(this);
+ NS_ADDREF(mLifecycleProxy); // Reference freed in DestroySubtree();
+}
+
+void IProtocol::DoomSubtree() {
+ MOZ_ASSERT(CanSend(), "dooming non-connected actor");
+ MOZ_ASSERT(mLifecycleProxy, "dooming zombie actor");
+
+ nsTArray<RefPtr<ActorLifecycleProxy>> managed;
+ AllManagedActors(managed);
+ for (ActorLifecycleProxy* proxy : managed) {
+ // Guard against actor being disconnected or destroyed during previous Doom
+ IProtocol* actor = proxy->Get();
+ if (actor && actor->CanSend()) {
+ actor->DoomSubtree();
+ }
+ }
+
+ // ActorDoom is called immediately before changing state, this allows messages
+ // to be sent during ActorDoom immediately before the channel is closed and
+ // sending messages is disabled.
+ ActorDoom();
+ mLinkStatus = LinkStatus::Doomed;
+}
+
+void IProtocol::DestroySubtree(ActorDestroyReason aWhy) {
+ MOZ_ASSERT(CanRecv(), "destroying non-connected actor");
+ MOZ_ASSERT(mLifecycleProxy, "destroying zombie actor");
+
+#ifdef FUZZING_SNAPSHOT
+ fuzzing::IPCFuzzController::instance().OnActorDestroyed(this);
+#endif
+
+ int32_t id = Id();
+
+ // If we're a managed actor, unregister from our manager
+ if (Manager()) {
+ Unregister(id);
+ }
+
+ // Destroy subtree
+ ActorDestroyReason subtreeWhy = aWhy;
+ if (aWhy == Deletion || aWhy == FailedConstructor) {
+ subtreeWhy = AncestorDeletion;
+ }
+
+ nsTArray<RefPtr<ActorLifecycleProxy>> managed;
+ AllManagedActors(managed);
+ for (ActorLifecycleProxy* proxy : managed) {
+ // Guard against actor being disconnected or destroyed during previous
+ // Destroy
+ IProtocol* actor = proxy->Get();
+ if (actor && actor->CanRecv()) {
+ actor->DestroySubtree(subtreeWhy);
+ }
+ }
+
+ // Ensure that we don't send any messages while we're calling `ActorDestroy`
+ // by setting our state to `Doomed`.
+ mLinkStatus = LinkStatus::Doomed;
+
+ // The actor is being destroyed, reject any pending responses, invoke
+ // `ActorDestroy` to destroy it, and then clear our status to
+ // `LinkStatus::Destroyed`.
+ GetIPCChannel()->RejectPendingResponsesForActor(id);
+ ActorDestroy(aWhy);
+ mLinkStatus = LinkStatus::Destroyed;
+}
+
+IToplevelProtocol::IToplevelProtocol(const char* aName, ProtocolId aProtoId,
+ Side aSide)
+ : IProtocol(aProtoId, aSide),
+ mOtherPid(base::kInvalidProcessId),
+ mLastLocalId(0),
+ mChannel(aName, this) {
+ mToplevel = this;
+}
+
+void IToplevelProtocol::SetOtherProcessId(base::ProcessId aOtherPid) {
+ mOtherPid = aOtherPid;
+}
+
+bool IToplevelProtocol::Open(ScopedPort aPort, const nsID& aMessageChannelId,
+ base::ProcessId aOtherPid,
+ nsISerialEventTarget* aEventTarget) {
+ SetOtherProcessId(aOtherPid);
+ return GetIPCChannel()->Open(std::move(aPort), mSide, aMessageChannelId,
+ aEventTarget);
+}
+
+bool IToplevelProtocol::Open(IToplevelProtocol* aTarget,
+ nsISerialEventTarget* aEventTarget,
+ mozilla::ipc::Side aSide) {
+ SetOtherProcessId(base::GetCurrentProcId());
+ aTarget->SetOtherProcessId(base::GetCurrentProcId());
+ return GetIPCChannel()->Open(aTarget->GetIPCChannel(), aEventTarget, aSide);
+}
+
+bool IToplevelProtocol::OpenOnSameThread(IToplevelProtocol* aTarget,
+ Side aSide) {
+ SetOtherProcessId(base::GetCurrentProcId());
+ aTarget->SetOtherProcessId(base::GetCurrentProcId());
+ return GetIPCChannel()->OpenOnSameThread(aTarget->GetIPCChannel(), aSide);
+}
+
+void IToplevelProtocol::NotifyImpendingShutdown() {
+ if (CanRecv()) {
+ GetIPCChannel()->NotifyImpendingShutdown();
+ }
+}
+
+void IToplevelProtocol::Close() { GetIPCChannel()->Close(); }
+
+void IToplevelProtocol::CloseWithError() { GetIPCChannel()->CloseWithError(); }
+
+void IToplevelProtocol::SetReplyTimeoutMs(int32_t aTimeoutMs) {
+ GetIPCChannel()->SetReplyTimeoutMs(aTimeoutMs);
+}
+
+bool IToplevelProtocol::IsOnCxxStack() const {
+ return GetIPCChannel()->IsOnCxxStack();
+}
+
+int32_t IToplevelProtocol::NextId() {
+ // Generate the next ID to use for a shared memory or protocol. Parent and
+ // Child sides of the protocol use different pools.
+ int32_t tag = 0;
+ if (GetSide() == ParentSide) {
+ tag |= 1 << 1;
+ }
+
+ // Check any overflow
+ MOZ_RELEASE_ASSERT(mLastLocalId < (1 << 29));
+
+ // Compute the ID to use with the low two bits as our tag, and the remaining
+ // bits as a monotonic.
+ return (++mLastLocalId << 2) | tag;
+}
+
+int32_t IToplevelProtocol::Register(IProtocol* aRouted) {
+ if (aRouted->Id() != kNullActorId && aRouted->Id() != kFreedActorId) {
+ // If there's already an ID, just return that.
+ return aRouted->Id();
+ }
+ return RegisterID(aRouted, NextId());
+}
+
+int32_t IToplevelProtocol::RegisterID(IProtocol* aRouted, int32_t aId) {
+ aRouted->SetId(aId);
+ aRouted->ActorConnected();
+ MOZ_ASSERT(!mActorMap.Contains(aId), "Don't insert with an existing ID");
+ mActorMap.InsertOrUpdate(aId, aRouted);
+ return aId;
+}
+
+IProtocol* IToplevelProtocol::Lookup(int32_t aId) { return mActorMap.Get(aId); }
+
+void IToplevelProtocol::Unregister(int32_t aId) {
+ MOZ_ASSERT(mActorMap.Contains(aId),
+ "Attempting to remove an ID not in the actor map");
+ mActorMap.Remove(aId);
+}
+
+Shmem::SharedMemory* IToplevelProtocol::CreateSharedMemory(size_t aSize,
+ bool aUnsafe,
+ Shmem::id_t* aId) {
+ RefPtr<Shmem::SharedMemory> segment(Shmem::Alloc(aSize, aUnsafe));
+ if (!segment) {
+ return nullptr;
+ }
+ int32_t id = NextId();
+ Shmem shmem(segment.get(), id);
+
+ UniquePtr<Message> descriptor = shmem.MkCreatedMessage(MSG_ROUTING_CONTROL);
+ if (!descriptor) {
+ return nullptr;
+ }
+ Unused << GetIPCChannel()->Send(std::move(descriptor));
+
+ *aId = shmem.Id();
+ Shmem::SharedMemory* rawSegment = segment.get();
+ MOZ_ASSERT(!mShmemMap.Contains(*aId), "Don't insert with an existing ID");
+ mShmemMap.InsertOrUpdate(*aId, segment.forget().take());
+ return rawSegment;
+}
+
+Shmem::SharedMemory* IToplevelProtocol::LookupSharedMemory(Shmem::id_t aId) {
+ return mShmemMap.Get(aId);
+}
+
+bool IToplevelProtocol::IsTrackingSharedMemory(Shmem::SharedMemory* segment) {
+ for (const auto& shmem : mShmemMap.Values()) {
+ if (segment == shmem) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool IToplevelProtocol::DestroySharedMemory(Shmem& shmem) {
+ Shmem::id_t aId = shmem.Id();
+ Shmem::SharedMemory* segment = LookupSharedMemory(aId);
+ if (!segment) {
+ return false;
+ }
+
+ UniquePtr<Message> descriptor = shmem.MkDestroyedMessage(MSG_ROUTING_CONTROL);
+
+ MOZ_ASSERT(mShmemMap.Contains(aId),
+ "Attempting to remove an ID not in the shmem map");
+ mShmemMap.Remove(aId);
+ Shmem::Dealloc(segment);
+
+ MessageChannel* channel = GetIPCChannel();
+ if (!channel->CanSend()) {
+ return true;
+ }
+
+ return descriptor && channel->Send(std::move(descriptor));
+}
+
+void IToplevelProtocol::DeallocShmems() {
+ for (const auto& shmem : mShmemMap.Values()) {
+ Shmem::Dealloc(shmem);
+ }
+ mShmemMap.Clear();
+}
+
+bool IToplevelProtocol::ShmemCreated(const Message& aMsg) {
+ Shmem::id_t id;
+ RefPtr<Shmem::SharedMemory> rawmem(Shmem::OpenExisting(aMsg, &id, true));
+ if (!rawmem) {
+ return false;
+ }
+ MOZ_ASSERT(!mShmemMap.Contains(id), "Don't insert with an existing ID");
+ mShmemMap.InsertOrUpdate(id, rawmem.forget().take());
+ return true;
+}
+
+bool IToplevelProtocol::ShmemDestroyed(const Message& aMsg) {
+ Shmem::id_t id;
+ MessageReader reader(aMsg);
+ if (!IPC::ReadParam(&reader, &id)) {
+ return false;
+ }
+ reader.EndRead();
+
+ Shmem::SharedMemory* rawmem = LookupSharedMemory(id);
+ if (rawmem) {
+ MOZ_ASSERT(mShmemMap.Contains(id),
+ "Attempting to remove an ID not in the shmem map");
+ mShmemMap.Remove(id);
+ Shmem::Dealloc(rawmem);
+ }
+ return true;
+}
+
+IPDLResolverInner::IPDLResolverInner(UniquePtr<IPC::Message> aReply,
+ IProtocol* aActor)
+ : mReply(std::move(aReply)),
+ mWeakProxy(aActor->GetLifecycleProxy()->GetWeakProxy()) {}
+
+void IPDLResolverInner::ResolveOrReject(
+ bool aResolve, FunctionRef<void(IPC::Message*, IProtocol*)> aWrite) {
+ MOZ_ASSERT(mWeakProxy);
+ MOZ_ASSERT(mWeakProxy->ActorEventTarget()->IsOnCurrentThread());
+ MOZ_ASSERT(mReply);
+
+ UniquePtr<IPC::Message> reply = std::move(mReply);
+
+ IProtocol* actor = mWeakProxy->Get();
+ if (!actor) {
+ NS_WARNING(nsPrintfCString("Not resolving response '%s': actor is dead",
+ reply->name())
+ .get());
+ return;
+ }
+
+ IPC::MessageWriter writer(*reply, actor);
+ WriteIPDLParam(&writer, actor, aResolve);
+ aWrite(reply.get(), actor);
+
+ actor->ChannelSend(std::move(reply));
+}
+
+void IPDLResolverInner::Destroy() {
+ if (mReply) {
+ NS_PROXY_DELETE_TO_EVENT_TARGET(IPDLResolverInner,
+ mWeakProxy->ActorEventTarget());
+ } else {
+ // If we've already been consumed, just delete without proxying. This avoids
+ // leaking the resolver if the actor's thread is already dead.
+ delete this;
+ }
+}
+
+IPDLResolverInner::~IPDLResolverInner() {
+ if (mReply) {
+ NS_WARNING(
+ nsPrintfCString(
+ "Rejecting reply '%s': resolver dropped without being called",
+ mReply->name())
+ .get());
+ ResolveOrReject(false, [](IPC::Message* aMessage, IProtocol* aActor) {
+ IPC::MessageWriter writer(*aMessage, aActor);
+ ResponseRejectReason reason = ResponseRejectReason::ResolverDestroyed;
+ WriteIPDLParam(&writer, aActor, reason);
+ });
+ }
+}
+
+} // namespace ipc
+} // namespace mozilla