Bug 1240985 - IPC fuzzer (r=gabor)
authorBill McCloskey <billm@mozilla.com>
Wed, 24 Feb 2016 14:04:44 -0800
changeset 323170 c82028946fee2461552a86559817e5299188f597
parent 323169 c9435de8c24caf7d7c1f28762021983bcc334e42
child 323171 7d63521081013dc30d53026efb89b8da3aab09e5
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgabor
bugs1240985
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1240985 - IPC fuzzer (r=gabor)
ipc/glue/MessageChannel.cpp
ipc/glue/MessageLink.h
ipc/ipdl/test/cxx/PTestDemon.ipdl
ipc/ipdl/test/cxx/TestDemon.cpp
ipc/ipdl/test/cxx/TestDemon.h
ipc/ipdl/test/cxx/moz.build
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -967,16 +967,20 @@ MessageChannel::Send(Message* aMsg, Mess
             IPC_LOG("Got reply: seqno=%d, xid=%d", seqno, transaction);
             break;
         }
 
         MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
 
         MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction);
         bool maybeTimedOut = !WaitForSyncNotify(handleWindowsMessages);
+        if (mListener->NeedArtificialSleep()) {
+            MonitorAutoUnlock unlock(*mMonitor);
+            mListener->ArtificialSleep();
+        }
 
         if (!Connected()) {
             ReportConnectionError("MessageChannel::SendAndWait");
             mLastSendError = SyncSendError::DisconnectedDuringSend;
             return false;
         }
 
         if (WasTransactionCanceled(transaction)) {
@@ -1371,31 +1375,37 @@ MessageChannel::DispatchMessage(const Me
 
         int id = aMsg.transaction_id();
         MOZ_RELEASE_ASSERT(!aMsg.is_sync() || id == mCurrentTransaction);
 
         {
             MonitorAutoUnlock unlock(*mMonitor);
             CxxStackFrame frame(*this, IN_MESSAGE, &aMsg);
 
+            mListener->ArtificialSleep();
+
             if (aMsg.is_sync())
                 DispatchSyncMessage(aMsg, *getter_Transfers(reply));
             else if (aMsg.is_interrupt())
                 DispatchInterruptMessage(aMsg, 0);
             else
                 DispatchAsyncMessage(aMsg);
+
+            mListener->ArtificialSleep();
         }
 
         if (mCurrentTransaction != id) {
             // The transaction has been canceled. Don't send a reply.
+            IPC_LOG("Nulling out reply due to cancellation, seqno=%d, xid=%d", aMsg.seqno(), id);
             reply = nullptr;
         }
     }
 
     if (reply && ChannelConnected == mChannelState) {
+        IPC_LOG("Sending reply seqno=%d, xid=%d", aMsg.seqno(), aMsg.transaction_id());
         mLink->SendMessage(reply.forget());
     }
 }
 
 void
 MessageChannel::DispatchSyncMessage(const Message& aMsg, Message*& aReply)
 {
     AssertWorkerThread();
@@ -1629,16 +1639,25 @@ MessageChannel::WaitResponse(bool aWaitT
     }
     return true;
 }
 
 #ifndef OS_WIN
 bool
 MessageChannel::WaitForSyncNotify(bool /* aHandleWindowsMessages */)
 {
+#ifdef DEBUG
+    // WARNING: We don't release the lock here. We can't because the link thread
+    // could signal at this time and we would miss it. Instead we require
+    // ArtificialTimeout() to be extremely simple.
+    if (mListener->ArtificialTimeout()) {
+        return false;
+    }
+#endif
+
     PRIntervalTime timeout = (kNoTimeout == mTimeoutMs) ?
                              PR_INTERVAL_NO_TIMEOUT :
                              PR_MillisecondsToInterval(mTimeoutMs);
     // XXX could optimize away this syscall for "no timeout" case if desired
     PRIntervalTime waitStart = PR_IntervalNow();
 
     mMonitor->Wait(timeout);
 
@@ -1665,16 +1684,17 @@ MessageChannel::ShouldContinueFromTimeou
 {
     AssertWorkerThread();
     mMonitor->AssertCurrentThreadOwns();
 
     bool cont;
     {
         MonitorAutoUnlock unlock(*mMonitor);
         cont = mListener->OnReplyTimeout();
+        mListener->ArtificialSleep();
     }
 
     static enum { UNKNOWN, NOT_DEBUGGING, DEBUGGING } sDebuggingChildren = UNKNOWN;
 
     if (sDebuggingChildren == UNKNOWN) {
         sDebuggingChildren = getenv("MOZ_DEBUG_CHILD_PROCESS") ? DEBUGGING : NOT_DEBUGGING;
     }
     if (sDebuggingChildren == DEBUGGING) {
--- a/ipc/glue/MessageLink.h
+++ b/ipc/glue/MessageLink.h
@@ -76,16 +76,43 @@ class MessageListener
         return false;
     }
 
     // WARNING: This function is called with the MessageChannel monitor held.
     virtual void IntentionalCrash() {
         MOZ_CRASH("Intentional IPDL crash");
     }
 
+    // The code here is only useful for fuzzing. It should not be used for any
+    // other purpose.
+#ifdef DEBUG
+    // Returns true if we should simulate a timeout.
+    // WARNING: This is a testing-only function that is called with the
+    // MessageChannel monitor held. Don't do anything fancy here or we could
+    // deadlock.
+    virtual bool ArtificialTimeout() {
+        return false;
+    }
+
+    // Returns true if we want to cause the worker thread to sleep with the
+    // monitor unlocked.
+    virtual bool NeedArtificialSleep() {
+        return false;
+    }
+
+    // This function should be implemented to sleep for some amount of time on
+    // the worker thread. Will only be called if NeedArtificialSleep() returns
+    // true.
+    virtual void ArtificialSleep() {}
+#else
+    bool ArtificialTimeout() { return false; }
+    bool NeedArtificialSleep() { return false; }
+    void ArtificialSleep() {}
+#endif
+
     virtual void OnEnteredCxxStack() {
         NS_RUNTIMEABORT("default impl shouldn't be invoked");
     }
     virtual void OnExitedCxxStack() {
         NS_RUNTIMEABORT("default impl shouldn't be invoked");
     }
     virtual void OnEnteredCall() {
         NS_RUNTIMEABORT("default impl shouldn't be invoked");
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/cxx/PTestDemon.ipdl
@@ -0,0 +1,21 @@
+namespace mozilla {
+namespace _ipdltest {
+
+prio(normal upto urgent) sync protocol PTestDemon
+{
+child:
+    async Start();
+
+both:
+    async AsyncMessage(int n);
+    prio(high) sync HiPrioSyncMessage();
+
+parent:
+    sync SyncMessage(int n);
+
+    prio(urgent) async UrgentAsyncMessage(int n);
+    prio(urgent) sync UrgentSyncMessage(int n);
+};
+
+} // namespace _ipdltest
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/cxx/TestDemon.cpp
@@ -0,0 +1,402 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=4 ts=4 et :
+ */
+#include "TestDemon.h"
+
+#include <stdlib.h>
+#include <sys/time.h>
+
+#include "IPDLUnitTests.h"      // fail etc.
+#if defined(OS_POSIX)
+#include <unistd.h>
+#else
+#include <windows.h>
+#endif
+
+template<>
+struct RunnableMethodTraits<mozilla::_ipdltest::TestDemonParent>
+{
+    static void RetainCallee(mozilla::_ipdltest::TestDemonParent* obj) { }
+    static void ReleaseCallee(mozilla::_ipdltest::TestDemonParent* obj) { }
+};
+
+template<>
+struct RunnableMethodTraits<mozilla::_ipdltest::TestDemonChild>
+{
+    static void RetainCallee(mozilla::_ipdltest::TestDemonChild* obj) { }
+    static void ReleaseCallee(mozilla::_ipdltest::TestDemonChild* obj) { }
+};
+
+namespace mozilla {
+namespace _ipdltest {
+
+const int kMaxStackHeight = 4;
+
+static LazyLogModule sLogModule("demon");
+
+#define DEMON_LOG(args...) MOZ_LOG(sLogModule, LogLevel::Debug, (args))
+
+static int gStackHeight = 0;
+static bool gFlushStack = false;
+
+static int
+Choose(int count)
+{
+  return random() % count;
+}
+
+//-----------------------------------------------------------------------------
+// parent
+
+TestDemonParent::TestDemonParent()
+ : mDone(false),
+   mIncoming(),
+   mOutgoing()
+{
+  MOZ_COUNT_CTOR(TestDemonParent);
+}
+
+TestDemonParent::~TestDemonParent()
+{
+  MOZ_COUNT_DTOR(TestDemonParent);
+}
+
+void
+TestDemonParent::Main()
+{
+  if (!getenv("MOZ_TEST_IPC_DEMON")) {
+    QuitParent();
+    return;
+  }
+  srandom(time(nullptr));
+
+  DEMON_LOG("Start demon");
+
+  if (!SendStart())
+	fail("sending Start");
+
+  RunUnlimitedSequence();
+}
+
+#ifdef DEBUG
+bool
+TestDemonParent::ShouldContinueFromReplyTimeout()
+{
+  return Choose(2) == 0;
+}
+
+bool
+TestDemonParent::ArtificialTimeout()
+{
+  return Choose(5) == 0;
+}
+
+void
+TestDemonParent::ArtificialSleep()
+{
+  if (Choose(2) == 0) {
+    // Sleep for anywhere from 0 to 100 milliseconds.
+    unsigned micros = Choose(100) * 1000;
+#ifdef OS_POSIX
+    usleep(micros);
+#else
+    Sleep(micros / 1000);
+#endif
+  }
+}
+#endif
+
+bool
+TestDemonParent::RecvAsyncMessage(const int& n)
+{
+  DEMON_LOG("Start RecvAsync [%d]", n);
+
+  MOZ_ASSERT(n == mIncoming[0]);
+  mIncoming[0]++;
+
+  RunLimitedSequence();
+
+  DEMON_LOG("End RecvAsync [%d]", n);
+  return true;
+}
+
+bool
+TestDemonParent::RecvHiPrioSyncMessage()
+{
+  DEMON_LOG("Start RecvHiPrioSyncMessage");
+  RunLimitedSequence();
+  DEMON_LOG("End RecvHiPrioSyncMessage");
+  return true;
+}
+
+bool
+TestDemonParent::RecvSyncMessage(const int& n)
+{
+  DEMON_LOG("Start RecvSync [%d]", n);
+
+  MOZ_ASSERT(n == mIncoming[0]);
+  mIncoming[0]++;
+
+  RunLimitedSequence(ASYNC_ONLY);
+
+  DEMON_LOG("End RecvSync [%d]", n);
+  return true;
+}
+
+bool
+TestDemonParent::RecvUrgentAsyncMessage(const int& n)
+{
+  DEMON_LOG("Start RecvUrgentAsyncMessage [%d]", n);
+
+  MOZ_ASSERT(n == mIncoming[2]);
+  mIncoming[2]++;
+
+  RunLimitedSequence(ASYNC_ONLY);
+
+  DEMON_LOG("End RecvUrgentAsyncMessage [%d]", n);
+  return true;
+}
+
+bool
+TestDemonParent::RecvUrgentSyncMessage(const int& n)
+{
+  DEMON_LOG("Start RecvUrgentSyncMessage [%d]", n);
+
+  MOZ_ASSERT(n == mIncoming[2]);
+  mIncoming[2]++;
+
+  RunLimitedSequence(ASYNC_ONLY);
+
+  DEMON_LOG("End RecvUrgentSyncMessage [%d]", n);
+  return true;
+}
+
+void
+TestDemonParent::RunUnlimitedSequence()
+{
+  if (mDone) {
+    return;
+  }
+
+  gFlushStack = false;
+  DoAction();
+
+  MessageLoop::current()->PostTask(FROM_HERE,
+                                   NewRunnableMethod(this, &TestDemonParent::RunUnlimitedSequence));
+}
+
+void
+TestDemonParent::RunLimitedSequence(int flags)
+{
+  if (gStackHeight >= kMaxStackHeight) {
+    return;
+  }
+  gStackHeight++;
+
+  int count = Choose(20);
+  for (int i = 0; i < count; i++) {
+	if (!DoAction(flags)) {
+      gFlushStack = true;
+    }
+    if (gFlushStack) {
+      gStackHeight--;
+      return;
+    }
+  }
+
+  gStackHeight--;
+}
+
+bool
+TestDemonParent::DoAction(int flags)
+{
+  if (flags & ASYNC_ONLY) {
+    DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
+    return SendAsyncMessage(mOutgoing[0]++);
+  } else {
+	switch (Choose(3)) {
+     case 0:
+      DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
+      return SendAsyncMessage(mOutgoing[0]++);
+
+     case 1: {
+       DEMON_LOG("Start SendHiPrioSyncMessage");
+       bool r = SendHiPrioSyncMessage();
+       DEMON_LOG("End SendHiPrioSyncMessage result=%d", r);
+       return r;
+     }
+
+     case 2:
+      DEMON_LOG("Cancel");
+      GetIPCChannel()->CancelCurrentTransaction();
+      return true;
+	}
+  }
+  MOZ_CRASH();
+  return false;
+}
+
+//-----------------------------------------------------------------------------
+// child
+
+
+TestDemonChild::TestDemonChild()
+ : mIncoming(),
+   mOutgoing()
+{
+  MOZ_COUNT_CTOR(TestDemonChild);
+}
+
+TestDemonChild::~TestDemonChild()
+{
+  MOZ_COUNT_DTOR(TestDemonChild);
+}
+
+bool
+TestDemonChild::RecvStart()
+{
+  srandom(time(nullptr));
+
+  DEMON_LOG("RecvStart");
+
+  RunUnlimitedSequence();
+  return true;
+}
+
+#ifdef DEBUG
+void
+TestDemonChild::ArtificialSleep()
+{
+  if (Choose(2) == 0) {
+    // Sleep for anywhere from 0 to 100 milliseconds.
+    unsigned micros = Choose(100) * 1000;
+#ifdef OS_POSIX
+    usleep(micros);
+#else
+    Sleep(micros / 1000);
+#endif
+  }
+}
+#endif
+
+bool
+TestDemonChild::RecvAsyncMessage(const int& n)
+{
+  DEMON_LOG("Start RecvAsyncMessage [%d]", n);
+
+  MOZ_ASSERT(n == mIncoming[0]);
+  mIncoming[0]++;
+
+  RunLimitedSequence();
+
+  DEMON_LOG("End RecvAsyncMessage [%d]", n);
+  return true;
+}
+
+bool
+TestDemonChild::RecvHiPrioSyncMessage()
+{
+  DEMON_LOG("Start RecvHiPrioSyncMessage");
+  RunLimitedSequence();
+  DEMON_LOG("End RecvHiPrioSyncMessage");
+  return true;
+}
+
+void
+TestDemonChild::RunUnlimitedSequence()
+{
+  gFlushStack = false;
+  DoAction();
+
+  MessageLoop::current()->PostTask(FROM_HERE,
+                                   NewRunnableMethod(this, &TestDemonChild::RunUnlimitedSequence));
+}
+
+void
+TestDemonChild::RunLimitedSequence()
+{
+  if (gStackHeight >= kMaxStackHeight) {
+    return;
+  }
+  gStackHeight++;
+
+  int count = Choose(20);
+  for (int i = 0; i < count; i++) {
+    if (!DoAction()) {
+      gFlushStack = true;
+    }
+    if (gFlushStack) {
+      gStackHeight--;
+      return;
+    }
+  }
+
+  gStackHeight--;
+}
+
+bool
+TestDemonChild::DoAction()
+{
+  switch (Choose(6)) {
+   case 0:
+	DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
+	return SendAsyncMessage(mOutgoing[0]++);
+
+   case 1: {
+     DEMON_LOG("Start SendHiPrioSyncMessage");
+     bool r = SendHiPrioSyncMessage();
+     DEMON_LOG("End SendHiPrioSyncMessage result=%d", r);
+     return r;
+   }
+
+   case 2: {
+     DEMON_LOG("Start SendSyncMessage [%d]", mOutgoing[0]);
+     bool r = SendSyncMessage(mOutgoing[0]++);
+     switch (GetIPCChannel()->LastSendError()) {
+       case SyncSendError::PreviousTimeout:
+       case SyncSendError::SendingCPOWWhileDispatchingSync:
+       case SyncSendError::SendingCPOWWhileDispatchingUrgent:
+       case SyncSendError::NotConnectedBeforeSend:
+       case SyncSendError::CancelledBeforeSend:
+         mOutgoing[0]--;
+         break;
+       default:
+         break;
+     }
+     DEMON_LOG("End SendSyncMessage result=%d", r);
+     return r;
+   }
+
+   case 3:
+	DEMON_LOG("SendUrgentAsyncMessage [%d]", mOutgoing[2]);
+	return SendUrgentAsyncMessage(mOutgoing[2]++);
+
+   case 4: {
+     DEMON_LOG("Start SendUrgentSyncMessage [%d]", mOutgoing[2]);
+     bool r = SendUrgentSyncMessage(mOutgoing[2]++);
+     switch (GetIPCChannel()->LastSendError()) {
+       case SyncSendError::PreviousTimeout:
+       case SyncSendError::SendingCPOWWhileDispatchingSync:
+       case SyncSendError::SendingCPOWWhileDispatchingUrgent:
+       case SyncSendError::NotConnectedBeforeSend:
+       case SyncSendError::CancelledBeforeSend:
+         mOutgoing[2]--;
+         break;
+       default:
+         break;
+     }
+     DEMON_LOG("End SendUrgentSyncMessage result=%d", r);
+     return r;
+   }
+
+   case 5:
+	DEMON_LOG("Cancel");
+	GetIPCChannel()->CancelCurrentTransaction();
+	return true;
+  }
+  MOZ_CRASH();
+  return false;
+}
+
+} // namespace _ipdltest
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/ipdl/test/cxx/TestDemon.h
@@ -0,0 +1,106 @@
+#ifndef mozilla__ipdltest_TestDemon_h
+#define mozilla__ipdltest_TestDemon_h 1
+
+#include "mozilla/_ipdltest/IPDLUnitTests.h"
+
+#include "mozilla/_ipdltest/PTestDemonParent.h"
+#include "mozilla/_ipdltest/PTestDemonChild.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace _ipdltest {
+
+
+class TestDemonParent :
+    public PTestDemonParent
+{
+public:
+    TestDemonParent();
+    virtual ~TestDemonParent();
+
+    static bool RunTestInProcesses() { return true; }
+    static bool RunTestInThreads() { return true; }
+
+    void Main();
+
+#ifdef DEBUG
+    bool ShouldContinueFromReplyTimeout() override;
+    bool ArtificialTimeout() override;
+
+    bool NeedArtificialSleep() override { return true; }
+    void ArtificialSleep() override;
+#endif
+
+    bool RecvAsyncMessage(const int& n) override;
+    bool RecvHiPrioSyncMessage() override;
+
+    bool RecvSyncMessage(const int& n) override;
+    bool RecvUrgentAsyncMessage(const int& n) override;
+    bool RecvUrgentSyncMessage(const int& n) override;
+
+    virtual void ActorDestroy(ActorDestroyReason why) override
+    {
+	mDone = true;
+	printf("Parent ActorDestroy\n");
+        passed("ok");
+        QuitParent();
+    }
+
+private:
+    bool mDone;
+    int mIncoming[3];
+    int mOutgoing[3];
+
+    enum {
+	ASYNC_ONLY = 1,
+    };
+
+    void RunUnlimitedSequence();
+    void RunLimitedSequence(int flags = 0);
+    bool DoAction(int flags = 0);
+};
+
+
+class TestDemonChild :
+    public PTestDemonChild
+{
+public:
+    TestDemonChild();
+    virtual ~TestDemonChild();
+
+    bool RecvStart() override;
+
+#ifdef DEBUG
+    bool NeedArtificialSleep() override { return true; }
+    void ArtificialSleep() override;
+#endif
+
+    bool RecvAsyncMessage(const int& n) override;
+    bool RecvHiPrioSyncMessage() override;
+
+    virtual void ActorDestroy(ActorDestroyReason why) override
+    {
+	_exit(0);
+    }
+
+    virtual void IntentionalCrash() override
+    {
+	_exit(0);
+    }
+
+private:
+    int mIncoming[3];
+    int mOutgoing[3];
+
+    void RunUnlimitedSequence();
+    void RunLimitedSequence();
+    bool DoAction();
+};
+
+
+} // namespace _ipdltest
+} // namespace mozilla
+
+
+#endif // ifndef mozilla__ipdltest_TestDemon_h
--- a/ipc/ipdl/test/cxx/moz.build
+++ b/ipc/ipdl/test/cxx/moz.build
@@ -15,16 +15,17 @@ EXPORTS.mozilla._ipdltest += [
 
 SOURCES += [
     'TestActorPunning.cpp',
     'TestBadActor.cpp',
     'TestBridgeMain.cpp',
     'TestCancel.cpp',
     'TestCrashCleanup.cpp',
     'TestDataStructures.cpp',
+    'TestDemon.cpp',
     'TestDesc.cpp',
     'TestEndpointBridgeMain.cpp',
     'TestEndpointOpens.cpp',
     'TestFailedCtor.cpp',
     'TestHangs.cpp',
     'TestHighestPrio.cpp',
     'TestInterruptErrorCleanup.cpp',
     'TestInterruptRaces.cpp',
@@ -68,16 +69,17 @@ IPDL_SOURCES += [
     'PTestBridgeMain.ipdl',
     'PTestBridgeMainSub.ipdl',
     'PTestBridgeSub.ipdl',
     'PTestCancel.ipdl',
     'PTestCrashCleanup.ipdl',
     'PTestDataStructures.ipdl',
     'PTestDataStructuresCommon.ipdlh',
     'PTestDataStructuresSub.ipdl',
+    'PTestDemon.ipdl',
     'PTestDesc.ipdl',
     'PTestDescSub.ipdl',
     'PTestDescSubsub.ipdl',
     'PTestEndpointBridgeMain.ipdl',
     'PTestEndpointBridgeMainSub.ipdl',
     'PTestEndpointBridgeSub.ipdl',
     'PTestEndpointOpens.ipdl',
     'PTestEndpointOpensOpened.ipdl',