michael@0: #include "TestInterruptErrorCleanup.h" michael@0: michael@0: #include "mozilla/CondVar.h" michael@0: #include "mozilla/Mutex.h" michael@0: michael@0: #include "IPDLUnitTests.h" // fail etc. michael@0: #include "IPDLUnitTestSubprocess.h" michael@0: michael@0: using mozilla::CondVar; michael@0: using mozilla::Mutex; michael@0: using mozilla::MutexAutoLock; michael@0: michael@0: namespace mozilla { michael@0: namespace _ipdltest { michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // parent michael@0: michael@0: namespace { michael@0: michael@0: // NB: this test does its own shutdown, rather than going through michael@0: // QuitParent(), because it's testing degenerate edge cases michael@0: michael@0: void DeleteSubprocess(Mutex* mutex, CondVar* cvar) michael@0: { michael@0: MutexAutoLock lock(*mutex); michael@0: michael@0: delete gSubprocess; michael@0: gSubprocess = nullptr; michael@0: michael@0: cvar->Notify(); michael@0: } michael@0: michael@0: void DeleteTheWorld() michael@0: { michael@0: delete static_cast(gParentActor); michael@0: gParentActor = nullptr; michael@0: michael@0: // needs to be synchronous to avoid affecting event ordering on michael@0: // the main thread michael@0: Mutex mutex("TestInterruptErrorCleanup.DeleteTheWorld.mutex"); michael@0: CondVar cvar(mutex, "TestInterruptErrorCleanup.DeleteTheWorld.cvar"); michael@0: michael@0: MutexAutoLock lock(mutex); michael@0: michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableFunction(DeleteSubprocess, &mutex, &cvar)); michael@0: michael@0: cvar.Wait(); michael@0: } michael@0: michael@0: void Done() michael@0: { michael@0: static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); michael@0: nsCOMPtr appShell (do_GetService(kAppShellCID)); michael@0: appShell->Exit(); michael@0: michael@0: passed(__FILE__); michael@0: } michael@0: michael@0: } // namespace michael@0: michael@0: TestInterruptErrorCleanupParent::TestInterruptErrorCleanupParent() michael@0: : mGotProcessingError(false) michael@0: { michael@0: MOZ_COUNT_CTOR(TestInterruptErrorCleanupParent); michael@0: } michael@0: michael@0: TestInterruptErrorCleanupParent::~TestInterruptErrorCleanupParent() michael@0: { michael@0: MOZ_COUNT_DTOR(TestInterruptErrorCleanupParent); michael@0: } michael@0: michael@0: void michael@0: TestInterruptErrorCleanupParent::Main() michael@0: { michael@0: // This test models the following sequence of events michael@0: // michael@0: // (1) Parent: Interrupt out-call michael@0: // (2) Child: crash michael@0: // --[Parent-only hereafter]-- michael@0: // (3) Interrupt out-call return false michael@0: // (4) Close() michael@0: // --[event loop]-- michael@0: // (5) delete parentActor michael@0: // (6) delete childProcess michael@0: // --[event loop]-- michael@0: // (7) Channel::OnError notification michael@0: // --[event loop]-- michael@0: // (8) Done, quit michael@0: // michael@0: // See bug 535298 and friends; this seqeunce of events captures michael@0: // three differnent potential errors michael@0: // - Close()-after-error (semantic error previously) michael@0: // - use-after-free of parentActor michael@0: // - use-after-free of channel michael@0: // michael@0: // Because of legacy constraints related to nsNPAPI* code, we need michael@0: // to ensure that this sequence of events can occur without michael@0: // errors/crashes. michael@0: michael@0: MessageLoop::current()->PostTask( michael@0: FROM_HERE, NewRunnableFunction(DeleteTheWorld)); michael@0: michael@0: // it's a failure if this *succeeds* michael@0: if (CallError()) michael@0: fail("expected an error!"); michael@0: michael@0: if (!mGotProcessingError) michael@0: fail("expected a ProcessingError() notification"); michael@0: michael@0: // it's OK to Close() a channel after an error, because nsNPAPI* michael@0: // wants to do this michael@0: Close(); michael@0: michael@0: // we know that this event *must* be after the MaybeError michael@0: // notification enqueued by AsyncChannel, because that event is michael@0: // enqueued within the same mutex that ends up signaling the michael@0: // wakeup-on-error of |CallError()| above michael@0: MessageLoop::current()->PostTask(FROM_HERE, NewRunnableFunction(Done)); michael@0: } michael@0: michael@0: void michael@0: TestInterruptErrorCleanupParent::ProcessingError(Result what) michael@0: { michael@0: if (what != MsgDropped) michael@0: fail("unexpected processing error"); michael@0: mGotProcessingError = true; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // child michael@0: michael@0: TestInterruptErrorCleanupChild::TestInterruptErrorCleanupChild() michael@0: { michael@0: MOZ_COUNT_CTOR(TestInterruptErrorCleanupChild); michael@0: } michael@0: michael@0: TestInterruptErrorCleanupChild::~TestInterruptErrorCleanupChild() michael@0: { michael@0: MOZ_COUNT_DTOR(TestInterruptErrorCleanupChild); michael@0: } michael@0: michael@0: bool michael@0: TestInterruptErrorCleanupChild::AnswerError() michael@0: { michael@0: _exit(0); michael@0: NS_RUNTIMEABORT("unreached"); michael@0: return false; michael@0: } michael@0: michael@0: michael@0: } // namespace _ipdltest michael@0: } // namespace mozilla