diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /ipc/glue/ProtocolUtils.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-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.cpp | 835 |
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 |