Bug 1093357 P2 Add an RAII type to safely handle serialized stream actors. r=jld

* * *
Bug 1093357 P2 interdiff 001 address review feedback
* * *
Bug 1093357 P2 interdiff 002 refactor to IPCStream type
This commit is contained in:
Ben Kelly 2016-05-15 10:32:09 -07:00
parent 749d2c1626
commit 4c8ab874ba
4 changed files with 682 additions and 0 deletions

34
ipc/glue/IPCStream.ipdlh Normal file
View File

@ -0,0 +1,34 @@
/* 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 protocol PSendStream;
include InputStreamParams;
namespace mozilla {
namespace ipc {
// Do not use this directly. See IPCStream below.
struct InputStreamParamsWithFds
{
InputStreamParams stream;
OptionalFileDescriptorSet optionalFds;
};
// Use IPCStream or OptionalIPCStream in your ipdl to represent serialized
// nsIInputStreams. Then use AutoIPCStream from IPCStreamUtils.h to perform
// the serialization.
union IPCStream
{
InputStreamParamsWithFds;
PSendStream;
};
union OptionalIPCStream
{
IPCStream;
void_t;
};
} // namespace ipc
} // namespace mozilla

462
ipc/glue/IPCStreamUtils.cpp Normal file
View File

@ -0,0 +1,462 @@
/* 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 "IPCStreamUtils.h"
#include "nsIIPCSerializableInputStream.h"
#include "mozilla/Assertions.h"
#include "mozilla/dom/PContentChild.h"
#include "mozilla/dom/File.h"
#include "mozilla/ipc/FileDescriptorSetChild.h"
#include "mozilla/ipc/FileDescriptorSetParent.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/ipc/SendStream.h"
#include "nsIAsyncInputStream.h"
namespace mozilla {
namespace ipc {
namespace {
// These serialization and cleanup functions could be externally exposed. For
// now, though, keep them private to encourage use of the safer RAII
// AutoIPCStream class.
template<typename M>
void
SerializeInputStreamWithFdsChild(nsIInputStream* aStream,
IPCStream& aValue,
M* aManager)
{
MOZ_ASSERT(aStream);
MOZ_ASSERT(aManager);
// First attempt simple stream serialization
nsCOMPtr<nsIIPCSerializableInputStream> serializable =
do_QueryInterface(aStream);
if (!serializable) {
MOZ_CRASH("Input stream is not serializable!");
}
aValue = InputStreamParamsWithFds();
InputStreamParamsWithFds& streamWithFds =
aValue.get_InputStreamParamsWithFds();
AutoTArray<FileDescriptor, 4> fds;
serializable->Serialize(streamWithFds.stream(), fds);
if (streamWithFds.stream().type() == InputStreamParams::T__None) {
MOZ_CRASH("Serialize failed!");
}
if (fds.IsEmpty()) {
streamWithFds.optionalFds() = void_t();
} else {
PFileDescriptorSetChild* fdSet =
aManager->SendPFileDescriptorSetConstructor(fds[0]);
for (uint32_t i = 1; i < fds.Length(); ++i) {
Unused << fdSet->SendAddFileDescriptor(fds[i]);
}
streamWithFds.optionalFds() = fdSet;
}
}
template<typename M>
void
SerializeInputStreamWithFdsParent(nsIInputStream* aStream,
IPCStream& aValue,
M* aManager)
{
MOZ_ASSERT(aStream);
MOZ_ASSERT(aManager);
// First attempt simple stream serialization
nsCOMPtr<nsIIPCSerializableInputStream> serializable =
do_QueryInterface(aStream);
if (!serializable) {
MOZ_CRASH("Input stream is not serializable!");
}
aValue = InputStreamParamsWithFds();
InputStreamParamsWithFds& streamWithFds =
aValue.get_InputStreamParamsWithFds();
AutoTArray<FileDescriptor, 4> fds;
serializable->Serialize(streamWithFds.stream(), fds);
if (streamWithFds.stream().type() == InputStreamParams::T__None) {
MOZ_CRASH("Serialize failed!");
}
streamWithFds.optionalFds() = void_t();
if (!fds.IsEmpty()) {
PFileDescriptorSetParent* fdSet =
aManager->SendPFileDescriptorSetConstructor(fds[0]);
for (uint32_t i = 1; i < fds.Length(); ++i) {
if (NS_WARN_IF(!fdSet->SendAddFileDescriptor(fds[i]))) {
Unused << PFileDescriptorSetParent::Send__delete__(fdSet);
fdSet = nullptr;
break;
}
}
if (fdSet) {
streamWithFds.optionalFds() = fdSet;
}
}
}
template<typename M>
void
SerializeInputStream(nsIInputStream* aStream, IPCStream& aValue, M* aManager)
{
MOZ_ASSERT(aStream);
MOZ_ASSERT(aManager);
// First attempt simple stream serialization
nsCOMPtr<nsIIPCSerializableInputStream> serializable =
do_QueryInterface(aStream);
if (serializable) {
SerializeInputStreamWithFdsChild(aStream, aValue, aManager);
return;
}
// As a fallback, attempt to stream the data across using a SendStream
// actor. This will fail for blocking streams.
nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(aStream);
aValue = SendStreamChild::Create(asyncStream, aManager);
if (!aValue.get_PSendStreamChild()) {
MOZ_CRASH("SendStream creation failed!");
}
}
template<typename M>
void
SerializeInputStream(nsIInputStream* aStream, OptionalIPCStream& aValue,
M* aManager)
{
if (!aStream) {
aValue = void_t();
return;
}
aValue = IPCStream();
SerializeInputStream(aStream, aValue.get_IPCStream(),
aManager);
}
void
CleanupIPCStream(IPCStream& aValue, bool aConsumedByIPC)
{
if (aValue.type() == IPCStream::T__None) {
return;
}
if (aValue.type() == IPCStream::TInputStreamParamsWithFds) {
InputStreamParamsWithFds& streamWithFds =
aValue.get_InputStreamParamsWithFds();
// Cleanup file descriptors if necessary
if (streamWithFds.optionalFds().type() ==
OptionalFileDescriptorSet::TPFileDescriptorSetChild) {
AutoTArray<FileDescriptor, 4> fds;
auto fdSetActor = static_cast<FileDescriptorSetChild*>(
streamWithFds.optionalFds().get_PFileDescriptorSetChild());
MOZ_ASSERT(fdSetActor);
if (!aConsumedByIPC) {
Unused << fdSetActor->Send__delete__(fdSetActor);
}
// FileDescriptorSet doesn't clear its fds in its ActorDestroy, so we
// unconditionally forget them here. The fds themselves are auto-closed in
// ~FileDescriptor since they originated in this process.
fdSetActor->ForgetFileDescriptors(fds);
} else if (streamWithFds.optionalFds().type() ==
OptionalFileDescriptorSet::TPFileDescriptorSetParent) {
AutoTArray<FileDescriptor, 4> fds;
auto fdSetActor = static_cast<FileDescriptorSetParent*>(
streamWithFds.optionalFds().get_PFileDescriptorSetParent());
MOZ_ASSERT(fdSetActor);
if (!aConsumedByIPC) {
Unused << fdSetActor->Send__delete__(fdSetActor);
}
// FileDescriptorSet doesn't clear its fds in its ActorDestroy, so we
// unconditionally forget them here. The fds themselves are auto-closed in
// ~FileDescriptor since they originated in this process.
fdSetActor->ForgetFileDescriptors(fds);
}
return;
}
MOZ_ASSERT(aValue.type() == IPCStream::TPSendStreamChild);
auto sendStream =
static_cast<SendStreamChild*>(aValue.get_PSendStreamChild());
if (!aConsumedByIPC) {
sendStream->StartDestroy();
return;
}
// If the SendStream was taken to be sent to the parent, then we need to
// start it before forgetting about it.
sendStream->Start();
}
void
CleanupIPCStream(OptionalIPCStream& aValue, bool aConsumedByIPC)
{
if (aValue.type() == OptionalIPCStream::Tvoid_t) {
return;
}
CleanupIPCStream(aValue.get_IPCStream(), aConsumedByIPC);
}
} // anonymous namespace
already_AddRefed<nsIInputStream>
DeserializeIPCStream(const IPCStream& aValue)
{
if (aValue.type() == IPCStream::TPSendStreamParent) {
auto sendStream =
static_cast<SendStreamParent*>(aValue.get_PSendStreamParent());
return sendStream->TakeReader();
}
// Note, we explicitly do not support deserializing the PSendStream actor on
// the child side. It can only be sent from child to parent.
MOZ_ASSERT(aValue.type() == IPCStream::TInputStreamParamsWithFds);
const InputStreamParamsWithFds& streamWithFds =
aValue.get_InputStreamParamsWithFds();
AutoTArray<FileDescriptor, 4> fds;
if (streamWithFds.optionalFds().type() ==
OptionalFileDescriptorSet::TPFileDescriptorSetParent) {
auto fdSetActor = static_cast<FileDescriptorSetParent*>(
streamWithFds.optionalFds().get_PFileDescriptorSetParent());
MOZ_ASSERT(fdSetActor);
fdSetActor->ForgetFileDescriptors(fds);
MOZ_ASSERT(!fds.IsEmpty());
if (!fdSetActor->Send__delete__(fdSetActor)) {
// child process is gone, warn and allow actor to clean up normally
NS_WARNING("Failed to delete fd set actor.");
}
} else if (streamWithFds.optionalFds().type() ==
OptionalFileDescriptorSet::TPFileDescriptorSetChild) {
auto fdSetActor = static_cast<FileDescriptorSetChild*>(
streamWithFds.optionalFds().get_PFileDescriptorSetChild());
MOZ_ASSERT(fdSetActor);
fdSetActor->ForgetFileDescriptors(fds);
MOZ_ASSERT(!fds.IsEmpty());
Unused << fdSetActor->Send__delete__(fdSetActor);
}
return DeserializeInputStream(streamWithFds.stream(), fds);
}
already_AddRefed<nsIInputStream>
DeserializeIPCStream(const OptionalIPCStream& aValue)
{
if (aValue.type() == OptionalIPCStream::Tvoid_t) {
return nullptr;
}
return DeserializeIPCStream(aValue.get_IPCStream());
}
namespace {
void
AssertValidValueToTake(const IPCStream& aVal)
{
MOZ_ASSERT(aVal.type() == IPCStream::TPSendStreamChild ||
aVal.type() == IPCStream::TInputStreamParamsWithFds);
}
void
AssertValidValueToTake(const OptionalIPCStream& aVal)
{
MOZ_ASSERT(aVal.type() == OptionalIPCStream::Tvoid_t ||
aVal.type() == OptionalIPCStream::TIPCStream);
if (aVal.type() == OptionalIPCStream::TIPCStream) {
AssertValidValueToTake(aVal.get_IPCStream());
}
}
} // anonymous namespace
AutoIPCStream::AutoIPCStream()
: mInlineValue(void_t())
, mValue(nullptr)
, mOptionalValue(&mInlineValue)
, mTaken(false)
{
}
AutoIPCStream::AutoIPCStream(IPCStream& aTarget)
: mInlineValue(void_t())
, mValue(&aTarget)
, mOptionalValue(nullptr)
, mTaken(false)
{
}
AutoIPCStream::AutoIPCStream(OptionalIPCStream& aTarget)
: mInlineValue(void_t())
, mValue(nullptr)
, mOptionalValue(&aTarget)
, mTaken(false)
{
*mOptionalValue = void_t();
}
AutoIPCStream::~AutoIPCStream()
{
MOZ_ASSERT(mValue || mOptionalValue);
if (mValue && IsSet()) {
CleanupIPCStream(*mValue, mTaken);
} else {
CleanupIPCStream(*mOptionalValue, mTaken);
}
}
void
AutoIPCStream::Serialize(nsIInputStream* aStream, PContentChild* aManager)
{
MOZ_ASSERT(aStream);
MOZ_ASSERT(aManager);
MOZ_ASSERT(mValue || mOptionalValue);
MOZ_ASSERT(!mTaken);
MOZ_ASSERT(!IsSet());
if (mValue) {
SerializeInputStream(aStream, *mValue, aManager);
AssertValidValueToTake(*mValue);
} else {
SerializeInputStream(aStream, *mOptionalValue, aManager);
AssertValidValueToTake(*mOptionalValue);
}
}
void
AutoIPCStream::Serialize(nsIInputStream* aStream, PBackgroundChild* aManager)
{
MOZ_ASSERT(aStream);
MOZ_ASSERT(aManager);
MOZ_ASSERT(mValue || mOptionalValue);
MOZ_ASSERT(!mTaken);
MOZ_ASSERT(!IsSet());
if (mValue) {
SerializeInputStream(aStream, *mValue, aManager);
AssertValidValueToTake(*mValue);
} else {
SerializeInputStream(aStream, *mOptionalValue, aManager);
AssertValidValueToTake(*mOptionalValue);
}
}
void
AutoIPCStream::Serialize(nsIInputStream* aStream, dom::PContentParent* aManager)
{
MOZ_ASSERT(aStream);
MOZ_ASSERT(aManager);
MOZ_ASSERT(mValue || mOptionalValue);
MOZ_ASSERT(!mTaken);
MOZ_ASSERT(!IsSet());
if (mValue) {
SerializeInputStreamWithFdsParent(aStream, *mValue, aManager);
AssertValidValueToTake(*mValue);
} else {
SerializeInputStreamWithFdsParent(aStream, *mOptionalValue, aManager);
AssertValidValueToTake(*mOptionalValue);
}
}
void
AutoIPCStream::Serialize(nsIInputStream* aStream, PBackgroundParent* aManager)
{
MOZ_ASSERT(aStream);
MOZ_ASSERT(aManager);
MOZ_ASSERT(mValue || mOptionalValue);
MOZ_ASSERT(!mTaken);
MOZ_ASSERT(!IsSet());
if (mValue) {
SerializeInputStreamWithFdsParent(aStream, *mValue, aManager);
AssertValidValueToTake(*mValue);
} else {
SerializeInputStreamWithFdsParent(aStream, *mOptionalValue, aManager);
AssertValidValueToTake(*mOptionalValue);
}
}
bool
AutoIPCStream::IsSet() const
{
MOZ_ASSERT(mValue || mOptionalValue);
if (mValue) {
return mValue->type() != IPCStream::T__None;
} else {
return mOptionalValue->type() != OptionalIPCStream::Tvoid_t &&
mOptionalValue->get_IPCStream().type() != IPCStream::T__None;
}
}
IPCStream&
AutoIPCStream::TakeValue()
{
MOZ_ASSERT(mValue || mOptionalValue);
MOZ_ASSERT(!mTaken);
MOZ_ASSERT(IsSet());
mTaken = true;
if (mValue) {
AssertValidValueToTake(*mValue);
return *mValue;
}
IPCStream& value =
mOptionalValue->get_IPCStream();
AssertValidValueToTake(value);
return value;
}
OptionalIPCStream&
AutoIPCStream::TakeOptionalValue()
{
MOZ_ASSERT(!mTaken);
MOZ_ASSERT(!mValue);
MOZ_ASSERT(mOptionalValue);
mTaken = true;
AssertValidValueToTake(*mOptionalValue);
return *mOptionalValue;
}
} // namespace ipc
} // namespace mozilla

183
ipc/glue/IPCStreamUtils.h Normal file
View File

@ -0,0 +1,183 @@
/* 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/. */
#ifndef mozilla_ipc_IPCStreamUtils_h
#define mozilla_ipc_IPCStreamUtils_h
#include "mozilla/ipc/IPCStream.h"
#include "nsIInputStream.h"
namespace mozilla {
namespace dom {
class PContentChild;
class PContentParent;
}
namespace ipc {
class PBackgroundChild;
class PBackgroundParent;
// Deserialize an IPCStream received from an actor call. These methods
// work in both the child and parent.
already_AddRefed<nsIInputStream>
DeserializeIPCStream(const IPCStream& aValue);
already_AddRefed<nsIInputStream>
DeserializeIPCStream(const OptionalIPCStream& aValue);
// RAII helper class that serializes an nsIInputStream into an IPCStream struct.
// Any file descriptor or PSendStream actors are automatically managed
// correctly.
//
// Here is a simple example:
//
// // in ipdl file
// Protocol PMyStuff
// {
// parent:
// async DoStuff(IPCStream aStream);
// child:
// async StuffDone(IPCStream aStream);
// };
//
// // in child c++ code
// void CallDoStuff(PMyStuffChild* aActor, nsIInputStream* aStream)
// {
// AutoIPCStream autoStream;
// autoStream.Serialize(aStream, aActor->Manager());
// aActor->SendDoStuff(autoStream.TakeValue());
// }
//
// // in parent c++ code
// bool
// MyStuffParent::RecvDoStuff(const IPCStream& aIPCStream) {
// nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aIPCStream);
// // Do something with stream...
//
// // You can also serialize streams from parent-to-child as long as
// // they don't require PSendStream actor support.
// AutoIPCStream anotherStream;
// anotherStream.Serialize(mFileStream, Manager());
// SendStuffDone(anotherStream.TakeValue());
// }
//
// The AutoIPCStream RAII class may also be used if your stream is embedded
// in a more complex IPDL structure. In this case you attach the AutoIPCStream
// to the embedded IPCStream and call TakeValue() after you pass the structure.
// For example:
//
// // in ipdl file
// struct Stuff
// {
// IPCStream stream;
// nsCString name;
// };
//
// Protocol PMyStuff
// {
// parent:
// async DoStuff(Stuff aStream);
// };
//
// // in child c++ code
// void CallDoStuff(PMyStuffChild* aActor, nsIInputStream* aStream)
// {
// Stuff stuff;
// AutoIPCStream autoStream(stuff.stream()); // attach to IPCStream here
// autoStream.Serialize(aStream, aActor->Manager());
// aActor->SendDoStuff(stuff);
// autoStream.TakeValue(); // call take value after send
// }
//
// // in parent c++ code
// bool
// MyStuffParent::RecvDoStuff(const Stuff& aStuff) {
// nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aStuff.stream());
// /* do something with the nsIInputStream */
// }
//
// The AutoIPCStream class also supports OptionalIPCStream values. As long as
// you did not initialize the object with a non-optional IPCStream, you can call
// TakeOptionalValue() instead.
//
// The AutoIPCStream class can also be used to serialize nsIInputStream objects
// on the parent side to send to the child. Currently, however, this only
// works for directly serializable stream types. The PSendStream actor mechanism
// is not supported in this direction yet.
//
// Like SerializeInputStream(), the AutoIPCStream will crash if
// serialization cannot be completed.
//
// NOTE: This is not a MOZ_STACK_CLASS so that it can be more easily integrated
// with complex ipdl structures. For example, you may want to create an
// array of RAII AutoIPCStream objects or build your own wrapping
// RAII object to handle other actors that need to be cleaned up.
class AutoIPCStream final
{
OptionalIPCStream mInlineValue;
IPCStream* mValue;
OptionalIPCStream* mOptionalValue;
bool mTaken;
bool
IsSet() const;
public:
// Implicitly create an OptionalIPCStream value. Either
// TakeValue() or TakeOptionalValue() can be used.
AutoIPCStream();
// Wrap an existing IPCStream. Only TakeValue() may be
// used. If a nullptr nsIInputStream is passed to SerializeOrSend() then
// a crash will be forced.
explicit AutoIPCStream(IPCStream& aTarget);
// Wrap an existing OptionalIPCStream. Either TakeValue()
// or TakeOptionalValue can be used.
explicit AutoIPCStream(OptionalIPCStream& aTarget);
~AutoIPCStream();
// Serialize the input stream or create a SendStream actor using the PContent
// manager. If neither of these succeed, then crash. This should only be
// used on the main thread.
void
Serialize(nsIInputStream* aStream, dom::PContentChild* aManager);
// Serialize the input stream or create a SendStream actor using the
// PBackground manager. If neither of these succeed, then crash. This can
// be called on the main thread or Worker threads.
void
Serialize(nsIInputStream* aStream, PBackgroundChild* aManager);
// Serialize the input stream. A PSendStream cannot be used when going
// from parent-to-child.
void
Serialize(nsIInputStream* aStream, dom::PContentParent* aManager);
// Serialize the input stream. A PSendStream cannot be used when going
// from parent-to-child.
void
Serialize(nsIInputStream* aStream, PBackgroundParent* aManager);
// Get the IPCStream as a non-optional value. This will
// assert if a stream has not been serialized or if it has already been taken.
// This should only be called if the value is being, or has already been, sent
// to the parent
IPCStream&
TakeValue();
// Get the OptionalIPCStream value. This will assert if
// the value has already been taken. This should only be called if the value
// is being, or has already been, sent to the parent
OptionalIPCStream&
TakeOptionalValue();
};
} // namespace ipc
} // namespace mozilla
#endif // mozilla_ipc_IPCStreamUtils_h

View File

@ -23,6 +23,7 @@ EXPORTS.mozilla.ipc += [
'GeckoChildProcessHost.h',
'InputStreamUtils.h',
'IOThreadChild.h',
'IPCStreamUtils.h',
'MessageChannel.h',
'MessageLink.h',
'Neutering.h',
@ -117,6 +118,7 @@ UNIFIED_SOURCES += [
'FileDescriptorUtils.cpp',
'InputStreamUtils.cpp',
'IPCMessageUtils.cpp',
'IPCStreamUtils.cpp',
'MessageChannel.cpp',
'MessageLink.cpp',
'MessagePump.cpp',
@ -158,6 +160,7 @@ LOCAL_INCLUDES += [
IPDL_SOURCES = [
'InputStreamParams.ipdlh',
'IPCStream.ipdlh',
'PBackground.ipdl',
'PBackgroundSharedTypes.ipdlh',
'PBackgroundTest.ipdl',