From 96163d083076e52dbe674cbf152846e46027edca Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Sun, 12 May 2019 13:16:36 -1000 Subject: [PATCH] Bug 1547084 Part 3 - C++ changes and removal for new control logic, r=loganfsmyth. --HG-- extra : rebase_source : e5c9c1aa48b8657b71527dce273feddc57bd0e3b --- toolkit/recordreplay/Callback.cpp | 8 +- toolkit/recordreplay/MemorySnapshot.cpp | 67 +- toolkit/recordreplay/MemorySnapshot.h | 4 - toolkit/recordreplay/ProcessRecordReplay.cpp | 76 +- toolkit/recordreplay/ProcessRecordReplay.h | 27 +- .../recordreplay/ProcessRedirectDarwin.cpp | 12 +- toolkit/recordreplay/ProcessRewind.cpp | 94 +- toolkit/recordreplay/ProcessRewind.h | 62 +- toolkit/recordreplay/ThreadSnapshot.h | 4 +- toolkit/recordreplay/ipc/Channel.cpp | 49 +- toolkit/recordreplay/ipc/Channel.h | 160 +-- toolkit/recordreplay/ipc/ChildIPC.cpp | 153 +-- toolkit/recordreplay/ipc/ChildInternal.h | 91 +- toolkit/recordreplay/ipc/ChildNavigation.cpp | 1195 ----------------- toolkit/recordreplay/ipc/ChildProcess.cpp | 58 +- toolkit/recordreplay/ipc/JSControl.cpp | 979 ++++++-------- toolkit/recordreplay/ipc/JSControl.h | 200 +-- toolkit/recordreplay/ipc/ParentForwarding.cpp | 6 +- toolkit/recordreplay/ipc/ParentGraphics.cpp | 29 - toolkit/recordreplay/ipc/ParentIPC.cpp | 14 +- toolkit/recordreplay/ipc/ParentInternal.h | 12 +- toolkit/recordreplay/moz.build | 1 - 22 files changed, 616 insertions(+), 2685 deletions(-) delete mode 100644 toolkit/recordreplay/ipc/ChildNavigation.cpp diff --git a/toolkit/recordreplay/Callback.cpp b/toolkit/recordreplay/Callback.cpp index fd146fcbe37d..2be03122ec10 100644 --- a/toolkit/recordreplay/Callback.cpp +++ b/toolkit/recordreplay/Callback.cpp @@ -41,7 +41,7 @@ void BeginCallback(size_t aCallbackId) { Thread* thread = Thread::Current(); if (thread->IsMainThread()) { - child::EndIdleTime(); + js::EndIdleTime(); } thread->SetPassThrough(false); @@ -59,7 +59,7 @@ void EndCallback() { Thread* thread = Thread::Current(); if (thread->IsMainThread()) { - child::BeginIdleTime(); + js::BeginIdleTime(); } thread->SetPassThrough(true); } @@ -99,12 +99,12 @@ void PassThroughThreadEventsAllowCallbacks(const std::function& aFn) { if (IsRecording()) { if (thread->IsMainThread()) { - child::BeginIdleTime(); + js::BeginIdleTime(); } thread->SetPassThrough(true); aFn(); if (thread->IsMainThread()) { - child::EndIdleTime(); + js::EndIdleTime(); } thread->SetPassThrough(false); thread->Events().RecordOrReplayThreadEvent(ThreadEvent::CallbacksFinished); diff --git a/toolkit/recordreplay/MemorySnapshot.cpp b/toolkit/recordreplay/MemorySnapshot.cpp index 5cfeb9eecc5b..a2b4cbed51ec 100644 --- a/toolkit/recordreplay/MemorySnapshot.cpp +++ b/toolkit/recordreplay/MemorySnapshot.cpp @@ -161,7 +161,7 @@ typedef SplayTree> mPages; - explicit DirtyPageSet(const CheckpointId& aCheckpoint) + explicit DirtyPageSet(size_t aCheckpoint) : mCheckpoint(aCheckpoint) {} }; @@ -349,17 +349,10 @@ struct MemoryInfo { // Recent dirty memory faults. void* mDirtyMemoryFaults[50]; - // Whether RecordReplayDirective may crash this process. - bool mIntentionalCrashesAllowed; - - // Whether the CrashSoon directive has been given to this process. - bool mCrashSoon; - MemoryInfo() : mMemoryChangesAllowed(true), mFreeUntrackedRegions(MemoryKind::FreeRegions), - mStartTime(CurrentTime()), - mIntentionalCrashesAllowed(true) { + mStartTime(CurrentTime()) { // The singleton MemoryInfo is allocated with zeroed memory, so other // fields do not need explicit initialization. } @@ -431,42 +424,6 @@ void DumpTimers() { } } -/////////////////////////////////////////////////////////////////////////////// -// Directives -/////////////////////////////////////////////////////////////////////////////// - -void SetAllowIntentionalCrashes(bool aAllowed) { - gMemoryInfo->mIntentionalCrashesAllowed = aAllowed; -} - -extern "C" { - -MOZ_EXPORT void RecordReplayInterface_InternalRecordReplayDirective( - long aDirective) { - switch ((Directive)aDirective) { - case Directive::CrashSoon: - gMemoryInfo->mCrashSoon = true; - break; - case Directive::MaybeCrash: - if (gMemoryInfo->mIntentionalCrashesAllowed && gMemoryInfo->mCrashSoon) { - PrintSpew("Intentionally Crashing!\n"); - MOZ_CRASH("RecordReplayDirective intentional crash"); - } - gMemoryInfo->mCrashSoon = false; - break; - case Directive::AlwaysSaveTemporaryCheckpoints: - navigation::AlwaysSaveTemporaryCheckpoints(); - break; - case Directive::AlwaysMarkMajorCheckpoints: - child::NotifyAlwaysMarkMajorCheckpoints(); - break; - default: - MOZ_CRASH("Unknown directive"); - } -} - -} // extern "C" - /////////////////////////////////////////////////////////////////////////////// // Snapshot Thread Conditions /////////////////////////////////////////////////////////////////////////////// @@ -645,7 +602,7 @@ void UnrecoverableSnapshotFailure() { /////////////////////////////////////////////////////////////////////////////// void AddInitialUntrackedMemoryRegion(uint8_t* aBase, size_t aSize) { - MOZ_RELEASE_ASSERT(!HasSavedCheckpoint()); + MOZ_RELEASE_ASSERT(!HasSavedAnyCheckpoint()); if (gInitializationFailureMessage) { return; @@ -675,7 +632,7 @@ void AddInitialUntrackedMemoryRegion(uint8_t* aBase, size_t aSize) { } static void RemoveInitialUntrackedRegion(uint8_t* aBase, size_t aSize) { - MOZ_RELEASE_ASSERT(!HasSavedCheckpoint()); + MOZ_RELEASE_ASSERT(!HasSavedAnyCheckpoint()); AutoSpinLock lock(gMemoryInfo->mInitialUntrackedRegionsLock); for (AllocatedMemoryRegion& region : gMemoryInfo->mInitialUntrackedRegions) { @@ -1042,10 +999,10 @@ void RegisterAllocatedMemory(void* aBaseAddress, size_t aSize, uint8_t* aAddress = reinterpret_cast(aBaseAddress); if (aKind != MemoryKind::Tracked) { - if (!HasSavedCheckpoint()) { + if (!HasSavedAnyCheckpoint()) { AddInitialUntrackedMemoryRegion(aAddress, aSize); } - } else if (HasSavedCheckpoint()) { + } else if (HasSavedAnyCheckpoint()) { EnsureMemoryChangesAllowed(); DirectWriteProtectMemory(aAddress, aSize, true); AddTrackedRegion(aAddress, aSize, true); @@ -1056,7 +1013,7 @@ void CheckFixedMemory(void* aAddress, size_t aSize) { MOZ_RELEASE_ASSERT(aAddress == PageBase(aAddress)); MOZ_RELEASE_ASSERT(aSize == RoundupSizeToPageBoundary(aSize)); - if (!HasSavedCheckpoint()) { + if (!HasSavedAnyCheckpoint()) { return; } @@ -1087,7 +1044,7 @@ void RestoreWritableFixedMemory(void* aAddress, size_t aSize) { MOZ_RELEASE_ASSERT(aAddress == PageBase(aAddress)); MOZ_RELEASE_ASSERT(aSize == RoundupSizeToPageBoundary(aSize)); - if (!HasSavedCheckpoint()) { + if (!HasSavedAnyCheckpoint()) { return; } @@ -1108,7 +1065,7 @@ void* AllocateMemoryTryAddress(void* aAddress, size_t aSize, MemoryKind aKind) { gMemoryInfo->mMemoryBalance[(size_t)aKind] += aSize; } - if (HasSavedCheckpoint()) { + if (HasSavedAnyCheckpoint()) { if (void* res = FreeRegionSet::Get(aKind).Extract(aAddress, aSize)) { return res; } @@ -1141,7 +1098,7 @@ void DeallocateMemory(void* aAddress, size_t aSize, MemoryKind aKind) { } // Memory is returned to the system before saving the first checkpoint. - if (!HasSavedCheckpoint()) { + if (!HasSavedAnyCheckpoint()) { if (IsReplaying() && aKind != MemoryKind::Tracked) { RemoveInitialUntrackedRegion((uint8_t*)aAddress, aSize); } @@ -1172,7 +1129,7 @@ void DeallocateMemory(void* aAddress, size_t aSize, MemoryKind aKind) { // this thread which were modified since the last recorded diff snapshot. static void SnapshotThreadRestoreLastDiffSnapshot( SnapshotThreadWorklist* aWorklist) { - CheckpointId checkpoint = GetLastSavedCheckpoint(); + size_t checkpoint = GetLastSavedCheckpoint(); DirtyPageSet& set = aWorklist->mSets.back(); MOZ_RELEASE_ASSERT(set.mCheckpoint == checkpoint); diff --git a/toolkit/recordreplay/MemorySnapshot.h b/toolkit/recordreplay/MemorySnapshot.h index 505e61b6a10c..d9a8d4d0e194 100644 --- a/toolkit/recordreplay/MemorySnapshot.h +++ b/toolkit/recordreplay/MemorySnapshot.h @@ -102,10 +102,6 @@ void UnrecoverableSnapshotFailure(); // was taken as free. void FixupFreeRegionsAfterRewind(); -// Set whether to allow intentionally crashing in this process via the -// RecordReplayDirective method. -void SetAllowIntentionalCrashes(bool aAllowed); - // When WANT_COUNTDOWN_THREAD is defined (see MemorySnapshot.cpp), set a count // that, after a thread consumes it, causes the thread to report a fatal error. // This is used for debugging and is a workaround for lldb often being unable diff --git a/toolkit/recordreplay/ProcessRecordReplay.cpp b/toolkit/recordreplay/ProcessRecordReplay.cpp index f1b4769e223a..480a28df35cd 100644 --- a/toolkit/recordreplay/ProcessRecordReplay.cpp +++ b/toolkit/recordreplay/ProcessRecordReplay.cpp @@ -56,6 +56,9 @@ static int gRecordingPid; // Whether to spew record/replay messages to stderr. static bool gSpewEnabled; +// Whether this is the main child. +static bool gMainChild; + extern "C" { MOZ_EXPORT void RecordReplayInterface_Initialize(int aArgc, char* aArgv[]) { @@ -95,16 +98,20 @@ MOZ_EXPORT void RecordReplayInterface_Initialize(int aArgc, char* aArgv[]) { MOZ_CRASH("Bad ProcessKind"); } - if (IsRecordingOrReplaying() && TestEnv("WAIT_AT_START")) { + if (IsRecording() && TestEnv("MOZ_RECORDING_WAIT_AT_START")) { BusyWait(); } - if (IsMiddleman() && TestEnv("MIDDLEMAN_WAIT_AT_START")) { + if (IsReplaying() && TestEnv("MOZ_REPLAYING_WAIT_AT_START")) { + BusyWait(); + } + + if (IsMiddleman() && TestEnv("MOZ_MIDDLEMAN_WAIT_AT_START")) { BusyWait(); } gPid = getpid(); - if (TestEnv("RECORD_REPLAY_SPEW")) { + if (TestEnv("MOZ_RECORD_REPLAY_SPEW")) { gSpewEnabled = true; } @@ -159,6 +166,8 @@ MOZ_EXPORT void RecordReplayInterface_Initialize(int aArgc, char* aArgv[]) { InitializeRewindState(); gRecordingPid = RecordReplayValue(gPid); + gMainChild = IsRecording(); + gInitialized = true; } @@ -201,25 +210,18 @@ MOZ_EXPORT void RecordReplayInterface_InternalInvalidateRecording( } // extern "C" -// How many recording endpoints have been flushed to the recording. -static size_t gNumEndpoints; - void FlushRecording() { MOZ_RELEASE_ASSERT(IsRecording()); MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread()); - // Save the endpoint of the recording. - js::ExecutionPoint endpoint = navigation::GetRecordingEndpoint(); + // The recording can only be flushed when we are at a checkpoint. + // Save this endpoint to the recording. + size_t endpoint = GetLastCheckpoint(); Stream* endpointStream = gRecordingFile->OpenStream(StreamName::Main, 0); - endpointStream->WriteScalar(++gNumEndpoints); - endpointStream->WriteBytes(&endpoint, sizeof(endpoint)); + endpointStream->WriteScalar(endpoint); gRecordingFile->PreventStreamWrites(); - gRecordingFile->Flush(); - - child::NotifyFlushedRecording(); - gRecordingFile->AllowStreamWrites(); } @@ -246,34 +248,6 @@ static bool LoadNextRecordingIndex() { return found; } -bool HitRecordingEndpoint() { - MOZ_RELEASE_ASSERT(IsReplaying()); - MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread()); - - // The debugger will call this method in a loop, so we don't have to do - // anything fancy to try to get the most up to date endpoint. As long as we - // can make some progress in attempting to find a later endpoint, we can - // return control to the debugger. - - // Check if there is a new endpoint in the endpoint data stream. - Stream* endpointStream = gRecordingFile->OpenStream(StreamName::Main, 0); - if (!endpointStream->AtEnd()) { - js::ExecutionPoint endpoint; - size_t index = endpointStream->ReadScalar(); - endpointStream->ReadBytes(&endpoint, sizeof(endpoint)); - navigation::SetRecordingEndpoint(index, endpoint); - return true; - } - - // Check if there is more data in the recording. - if (LoadNextRecordingIndex()) { - return true; - } - - // OK, we hit the most up to date endpoint in the recording. - return false; -} - void HitEndOfRecording() { MOZ_RELEASE_ASSERT(IsReplaying()); MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough()); @@ -290,6 +264,21 @@ void HitEndOfRecording() { } } +// When replaying, the last endpoint loaded from the recording. +static size_t gRecordingEndpoint; + +size_t RecordingEndpoint() { + MOZ_RELEASE_ASSERT(IsReplaying()); + MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough()); + + Stream* endpointStream = gRecordingFile->OpenStream(StreamName::Main, 0); + while (!endpointStream->AtEnd()) { + gRecordingEndpoint = endpointStream->ReadScalar(); + } + + return gRecordingEndpoint; +} + bool SpewEnabled() { return gSpewEnabled; } void InternalPrint(const char* aFormat, va_list aArgs) { @@ -315,6 +304,9 @@ const char* ThreadEventName(ThreadEvent aEvent) { int GetRecordingPid() { return gRecordingPid; } +bool IsMainChild() { return gMainChild; } +void SetMainChild() { gMainChild = true; } + /////////////////////////////////////////////////////////////////////////////// // Record/Replay Assertions /////////////////////////////////////////////////////////////////////////////// diff --git a/toolkit/recordreplay/ProcessRecordReplay.h b/toolkit/recordreplay/ProcessRecordReplay.h index 5a6e00c6ee9a..7e97d205845c 100644 --- a/toolkit/recordreplay/ProcessRecordReplay.h +++ b/toolkit/recordreplay/ProcessRecordReplay.h @@ -107,24 +107,13 @@ void FlushRecording(); // Called when any thread hits the end of its event stream. void HitEndOfRecording(); -// Called when the main thread hits the latest recording endpoint it knows -// about. -bool HitRecordingEndpoint(); +// Called in a replaying process to load the last checkpoint in the recording. +size_t RecordingEndpoint(); -// Possible directives to give via the RecordReplayDirective function. -enum class Directive { - // Crash at the next use of MaybeCrash. - CrashSoon = 1, - - // Irrevocably crash if CrashSoon has ever been used on the process. - MaybeCrash = 2, - - // Always save temporary checkpoints when stepping around in the debugger. - AlwaysSaveTemporaryCheckpoints = 3, - - // Mark all future checkpoints as major checkpoints in the middleman. - AlwaysMarkMajorCheckpoints = 4 -}; +// Access the flag for whether this is the main child. The main child never +// rewinds and sends graphics updates to the middleman while running forward. +bool IsMainChild(); +void SetMainChild(); // Get the process kind and recording file specified at the command line. // These are available in the middleman as well as while recording/replaying. @@ -283,8 +272,8 @@ enum class MemoryKind { SortedDirtyPageSet, PageCopy, - // Memory used for navigation state. - Navigation, + // Memory used by various parts of JS integration. + ScriptHits, Count }; diff --git a/toolkit/recordreplay/ProcessRedirectDarwin.cpp b/toolkit/recordreplay/ProcessRedirectDarwin.cpp index b8fd9d21a817..b02cc61f3027 100644 --- a/toolkit/recordreplay/ProcessRedirectDarwin.cpp +++ b/toolkit/recordreplay/ProcessRedirectDarwin.cpp @@ -404,7 +404,7 @@ static PreambleResult MiddlemanPreamble_sendmsg(CallArguments* aArguments) { static PreambleResult Preamble_mprotect(CallArguments* aArguments) { // Ignore any mprotect calls that occur after saving a checkpoint. - if (!HasSavedCheckpoint()) { + if (!HasSavedAnyCheckpoint()) { return PreambleResult::PassThrough; } aArguments->Rval() = 0; @@ -432,7 +432,7 @@ static PreambleResult Preamble_mmap(CallArguments* aArguments) { // Get an anonymous mapping for the result. if (flags & MAP_FIXED) { // For fixed allocations, make sure this memory region is mapped and zero. - if (!HasSavedCheckpoint()) { + if (!HasSavedAnyCheckpoint()) { // Make sure this memory region is writable. CallFunction(gOriginal_mprotect, address, size, PROT_READ | PROT_WRITE | PROT_EXEC); @@ -448,7 +448,7 @@ static PreambleResult Preamble_mmap(CallArguments* aArguments) { // for memory that is already allocated. If we haven't saved a checkpoint // then this is no problem, but after saving a checkpoint we have to make // sure that protection flags are what we expect them to be. - int newProt = HasSavedCheckpoint() ? (PROT_READ | PROT_EXEC) : prot; + int newProt = HasSavedAnyCheckpoint() ? (PROT_READ | PROT_EXEC) : prot; memory = CallFunction(gOriginal_mmap, address, size, newProt, flags, fd, offset); @@ -674,9 +674,11 @@ static ssize_t WaitForCvar(pthread_mutex_t* aMutex, pthread_cond_t* aCond, static PreambleResult Preamble_pthread_cond_wait(CallArguments* aArguments) { auto& cond = aArguments->Arg<0, pthread_cond_t*>(); auto& mutex = aArguments->Arg<1, pthread_mutex_t*>(); + js::BeginIdleTime(); aArguments->Rval() = WaitForCvar(mutex, cond, false, [=]() { return CallFunction(gOriginal_pthread_cond_wait, cond, mutex); }); + js::EndIdleTime(); return PreambleResult::Veto; } @@ -925,7 +927,7 @@ static PreambleResult Preamble_mach_vm_map(CallArguments* aArguments) { } else if (AreThreadEventsPassedThrough()) { // We should only reach this at startup, when initializing the graphics // shared memory block. - MOZ_RELEASE_ASSERT(!HasSavedCheckpoint()); + MOZ_RELEASE_ASSERT(!HasSavedAnyCheckpoint()); return PreambleResult::PassThrough; } @@ -940,7 +942,7 @@ static PreambleResult Preamble_mach_vm_map(CallArguments* aArguments) { static PreambleResult Preamble_mach_vm_protect(CallArguments* aArguments) { // Ignore any mach_vm_protect calls that occur after saving a checkpoint, as // for mprotect. - if (!HasSavedCheckpoint()) { + if (!HasSavedAnyCheckpoint()) { return PreambleResult::PassThrough; } aArguments->Rval() = KERN_SUCCESS; diff --git a/toolkit/recordreplay/ProcessRewind.cpp b/toolkit/recordreplay/ProcessRewind.cpp index 8a079feec31f..eaff284cb5f8 100644 --- a/toolkit/recordreplay/ProcessRewind.cpp +++ b/toolkit/recordreplay/ProcessRewind.cpp @@ -24,11 +24,7 @@ namespace recordreplay { // are in untracked memory. struct RewindInfo { // The most recent checkpoint which was encountered. - CheckpointId mLastCheckpoint; - - // Whether this is the active child process. See the comment under - // 'Child Roles' in ParentIPC.cpp. - bool mIsActiveChild; + size_t mLastCheckpoint; // Checkpoints which have been saved. This includes only entries from // mShouldSaveCheckpoints, plus all temporary checkpoints. @@ -55,22 +51,23 @@ void InitializeRewindState() { void* memory = AllocateMemory(sizeof(RewindInfo), MemoryKind::Generic); gRewindInfo = new (memory) RewindInfo(); + // The first checkpoint is implicitly saved while replaying: we won't be able + // to get a manifest from the middleman telling us what to save until after + // this checkpoint has been reached. + if (IsReplaying()) { + gRewindInfo->mShouldSaveCheckpoints.append(FirstCheckpointId); + } + gMainThreadCallbackMonitor = new Monitor(); } -static bool CheckpointPrecedes(const CheckpointId& aFirst, - const CheckpointId& aSecond) { - return aFirst.mNormal < aSecond.mNormal || - aFirst.mTemporary < aSecond.mTemporary; -} - -void RestoreCheckpointAndResume(const CheckpointId& aCheckpoint) { +void RestoreCheckpointAndResume(size_t aCheckpoint) { MOZ_RELEASE_ASSERT(IsReplaying()); MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread()); MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough()); MOZ_RELEASE_ASSERT( aCheckpoint == gRewindInfo->mLastCheckpoint || - CheckpointPrecedes(aCheckpoint, gRewindInfo->mLastCheckpoint)); + aCheckpoint < gRewindInfo->mLastCheckpoint); // Make sure we don't lose pending main thread callbacks due to rewinding. { @@ -85,10 +82,10 @@ void RestoreCheckpointAndResume(const CheckpointId& aCheckpoint) { { // Rewind heap memory to the target checkpoint, which must have been saved. AutoDisallowMemoryChanges disallow; - CheckpointId newCheckpoint = + size_t newCheckpoint = gRewindInfo->mSavedCheckpoints.back().mCheckpoint; RestoreMemoryToLastSavedCheckpoint(); - while (CheckpointPrecedes(aCheckpoint, newCheckpoint)) { + while (aCheckpoint < newCheckpoint) { gRewindInfo->mSavedCheckpoints.back().ReleaseContents(); gRewindInfo->mSavedCheckpoints.popBack(); RestoreMemoryToLastSavedDiffCheckpoint(); @@ -100,10 +97,8 @@ void RestoreCheckpointAndResume(const CheckpointId& aCheckpoint) { FixupFreeRegionsAfterRewind(); double end = CurrentTime(); - PrintSpew("Restore #%d:%d -> #%d:%d %.2fs\n", - (int)gRewindInfo->mLastCheckpoint.mNormal, - (int)gRewindInfo->mLastCheckpoint.mTemporary, - (int)aCheckpoint.mNormal, (int)aCheckpoint.mTemporary, + PrintSpew("Restore #%d -> #%d %.2fs\n", + (int)gRewindInfo->mLastCheckpoint, (int)aCheckpoint, (end - start) / 1000000.0); // Finally, let threads restore themselves to their stacks at the checkpoint @@ -113,27 +108,24 @@ void RestoreCheckpointAndResume(const CheckpointId& aCheckpoint) { } void SetSaveCheckpoint(size_t aCheckpoint, bool aSave) { - MOZ_RELEASE_ASSERT(aCheckpoint > gRewindInfo->mLastCheckpoint.mNormal); + MOZ_RELEASE_ASSERT(aCheckpoint > gRewindInfo->mLastCheckpoint); VectorAddOrRemoveEntry(gRewindInfo->mShouldSaveCheckpoints, aCheckpoint, aSave); } -bool NewCheckpoint(bool aTemporary) { +bool NewCheckpoint() { MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread()); MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough()); MOZ_RELEASE_ASSERT(!HasDivergedFromRecording()); - MOZ_RELEASE_ASSERT(IsReplaying() || !aTemporary); - navigation::BeforeCheckpoint(); + js::BeforeCheckpoint(); // Get the ID of the new checkpoint. - CheckpointId checkpoint = - gRewindInfo->mLastCheckpoint.NextCheckpoint(aTemporary); + size_t checkpoint = gRewindInfo->mLastCheckpoint + 1; // Save all checkpoints the middleman tells us to, and temporary checkpoints // (which the middleman never knows about). - bool save = aTemporary || VectorContains(gRewindInfo->mShouldSaveCheckpoints, - checkpoint.mNormal); + bool save = VectorContains(gRewindInfo->mShouldSaveCheckpoints, checkpoint); bool reachedCheckpoint = true; if (save) { @@ -156,11 +148,10 @@ bool NewCheckpoint(bool aTemporary) { // Save all thread stacks for the checkpoint. If we rewind here from a // later point of execution then this will return false. if (SaveAllThreads(gRewindInfo->mSavedCheckpoints.back())) { - PrintSpew("Saved checkpoint #%d:%d %.2fs\n", (int)checkpoint.mNormal, - (int)checkpoint.mTemporary, (end - start) / 1000000.0); + PrintSpew("Saved checkpoint #%d %.2fs\n", (int)checkpoint, + (end - start) / 1000000.0); } else { - PrintSpew("Restored checkpoint #%d:%d\n", (int)checkpoint.mNormal, - (int)checkpoint.mTemporary); + PrintSpew("Restored checkpoint #%d\n", (int)checkpoint); reachedCheckpoint = false; @@ -171,11 +162,13 @@ bool NewCheckpoint(bool aTemporary) { } Thread::ResumeIdleThreads(); + } else { + PrintSpew("Skipping checkpoint #%d\n", (int)checkpoint); } gRewindInfo->mLastCheckpoint = checkpoint; - navigation::AfterCheckpoint(checkpoint); + js::AfterCheckpoint(checkpoint, !reachedCheckpoint); return reachedCheckpoint; } @@ -237,23 +230,29 @@ void EnsureNotDivergedFromRecording() { } } -bool HasSavedCheckpoint() { +bool HasSavedAnyCheckpoint() { return gRewindInfo && !gRewindInfo->mSavedCheckpoints.empty(); } -CheckpointId GetLastSavedCheckpoint() { - MOZ_RELEASE_ASSERT(HasSavedCheckpoint()); - return gRewindInfo->mSavedCheckpoints.back().mCheckpoint; -} - -CheckpointId GetLastSavedCheckpointPriorTo(const CheckpointId& aCheckpoint) { - MOZ_RELEASE_ASSERT(HasSavedCheckpoint()); - for (size_t i = gRewindInfo->mSavedCheckpoints.length() - 1; i >= 1; i--) { - if (gRewindInfo->mSavedCheckpoints[i].mCheckpoint == aCheckpoint) { - return gRewindInfo->mSavedCheckpoints[i - 1].mCheckpoint; +bool HasSavedCheckpoint(size_t aCheckpoint) { + if (!gRewindInfo) { + return false; + } + for (const SavedCheckpoint& saved : gRewindInfo->mSavedCheckpoints) { + if (saved.mCheckpoint == aCheckpoint) { + return true; } } - MOZ_CRASH("GetLastSavedCheckpointPriorTo"); + return false; +} + +size_t GetLastCheckpoint() { + return gRewindInfo ? gRewindInfo->mLastCheckpoint : InvalidCheckpointId; +} + +size_t GetLastSavedCheckpoint() { + MOZ_RELEASE_ASSERT(HasSavedAnyCheckpoint()); + return gRewindInfo->mSavedCheckpoints.back().mCheckpoint; } static bool gMainThreadShouldPause = false; @@ -262,7 +261,6 @@ bool MainThreadShouldPause() { return gMainThreadShouldPause; } void PauseMainThreadAndServiceCallbacks() { MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread()); - MOZ_RELEASE_ASSERT(!HasDivergedFromRecording()); AssertEventsAreNotPassedThrough(); // Whether there is a PauseMainThreadAndServiceCallbacks frame on the stack. @@ -273,6 +271,8 @@ void PauseMainThreadAndServiceCallbacks() { } gMainThreadIsPaused = true; + MOZ_RELEASE_ASSERT(!HasDivergedFromRecording()); + MonitorAutoLock lock(*gMainThreadCallbackMonitor); // Loop and invoke callbacks until one of them unpauses this thread. @@ -320,9 +320,5 @@ void ResumeExecution() { gMainThreadCallbackMonitor->Notify(); } -void SetIsActiveChild(bool aActive) { gRewindInfo->mIsActiveChild = aActive; } - -bool IsActiveChild() { return gRewindInfo->mIsActiveChild; } - } // namespace recordreplay } // namespace mozilla diff --git a/toolkit/recordreplay/ProcessRewind.h b/toolkit/recordreplay/ProcessRewind.h index 338ded1fbc98..9ad996c08557 100644 --- a/toolkit/recordreplay/ProcessRewind.h +++ b/toolkit/recordreplay/ProcessRewind.h @@ -79,39 +79,9 @@ namespace recordreplay { // rewind. /////////////////////////////////////////////////////////////////////////////// -// The ID of a checkpoint in a child process. Checkpoints are either normal or -// temporary. Normal checkpoints occur at the same point in the recording and -// all replays, while temporary checkpoints are not used while recording and -// may be at different points in different replays. -struct CheckpointId { - // ID of the most recent normal checkpoint, which are numbered in sequence - // starting at FirstCheckpointId. - size_t mNormal; - - // Special IDs for normal checkpoints. - static const size_t Invalid = 0; - static const size_t First = 1; - - // How many temporary checkpoints have been generated since the most recent - // normal checkpoint, zero if this represents the normal checkpoint itself. - size_t mTemporary; - - explicit CheckpointId(size_t aNormal = Invalid, size_t aTemporary = 0) - : mNormal(aNormal), mTemporary(aTemporary) {} - - inline bool operator==(const CheckpointId& o) const { - return mNormal == o.mNormal && mTemporary == o.mTemporary; - } - - inline bool operator!=(const CheckpointId& o) const { - return mNormal != o.mNormal || mTemporary != o.mTemporary; - } - - CheckpointId NextCheckpoint(bool aTemporary) const { - return CheckpointId(aTemporary ? mNormal : mNormal + 1, - aTemporary ? mTemporary + 1 : 0); - } -}; +// Special IDs for normal checkpoints. +static const size_t InvalidCheckpointId = 0; +static const size_t FirstCheckpointId = 1; // Initialize state needed for rewinding. void InitializeRewindState(); @@ -134,17 +104,20 @@ bool MainThreadShouldPause(); void PauseMainThreadAndServiceCallbacks(); // Return whether any checkpoints have been saved. -bool HasSavedCheckpoint(); +bool HasSavedAnyCheckpoint(); + +// Return whether a specific checkpoint has been saved. +bool HasSavedCheckpoint(size_t aCheckpoint); + +// Get the ID of the most recently encountered checkpoint. +size_t GetLastCheckpoint(); // Get the ID of the most recent saved checkpoint. -CheckpointId GetLastSavedCheckpoint(); - -// Get the ID of the saved checkpoint prior to aCheckpoint. -CheckpointId GetLastSavedCheckpointPriorTo(const CheckpointId& aCheckpoint); +size_t GetLastSavedCheckpoint(); // When paused at a breakpoint or at a checkpoint, restore a checkpoint that // was saved earlier and resume execution. -void RestoreCheckpointAndResume(const CheckpointId& aCheckpoint); +void RestoreCheckpointAndResume(size_t aCheckpoint); // When paused at a breakpoint or at a checkpoint, unpause and proceed with // execution. @@ -169,15 +142,10 @@ void DisallowUnhandledDivergeFromRecording(); // DivergeFromRecording, by rewinding to the last saved checkpoint if so. void EnsureNotDivergedFromRecording(); -// Access the flag for whether this is the active child process. -void SetIsActiveChild(bool aActive); -bool IsActiveChild(); - // Note a checkpoint at the current execution position. This checkpoint will be -// saved if either (a) it is temporary, or (b) the middleman has instructed -// this process to save this normal checkpoint. This method returns true if the -// checkpoint was just saved, and false if it was just restored. -bool NewCheckpoint(bool aTemporary); +// saved if it was instructed to do so via a manifest. This method returns true +// if the checkpoint was just saved, and false if it was just restored. +bool NewCheckpoint(); } // namespace recordreplay } // namespace mozilla diff --git a/toolkit/recordreplay/ThreadSnapshot.h b/toolkit/recordreplay/ThreadSnapshot.h index af9e0f3ed89b..87c07e979999 100644 --- a/toolkit/recordreplay/ThreadSnapshot.h +++ b/toolkit/recordreplay/ThreadSnapshot.h @@ -73,10 +73,10 @@ struct SavedThreadStack { }; struct SavedCheckpoint { - CheckpointId mCheckpoint; + size_t mCheckpoint; SavedThreadStack mStacks[MaxRecordedThreadId]; - explicit SavedCheckpoint(CheckpointId aCheckpoint) + explicit SavedCheckpoint(size_t aCheckpoint) : mCheckpoint(aCheckpoint) {} void ReleaseContents() { diff --git a/toolkit/recordreplay/ipc/Channel.cpp b/toolkit/recordreplay/ipc/Channel.cpp index 195a04afc871..082c5e7e7ceb 100644 --- a/toolkit/recordreplay/ipc/Channel.cpp +++ b/toolkit/recordreplay/ipc/Channel.cpp @@ -336,58 +336,19 @@ void Channel::PrintMessage(const char* aPrefix, const Message& aMsg) { AutoEnsurePassThroughThreadEvents pt; nsCString data; switch (aMsg.mType) { - case MessageType::HitExecutionPoint: { - const HitExecutionPointMessage& nmsg = - (const HitExecutionPointMessage&)aMsg; - nmsg.mPoint.ToString(data); - data.AppendPrintf(" Endpoint %d Duration %.2f ms", - nmsg.mRecordingEndpoint, - nmsg.mDurationMicroseconds / 1000.0); - break; - } - case MessageType::Resume: { - const ResumeMessage& nmsg = (const ResumeMessage&)aMsg; - data.AppendPrintf("Forward %d", nmsg.mForward); - break; - } - case MessageType::RestoreCheckpoint: { - const RestoreCheckpointMessage& nmsg = - (const RestoreCheckpointMessage&)aMsg; - data.AppendPrintf("Id %d", (int)nmsg.mCheckpoint); - break; - } - case MessageType::AddBreakpoint: { - const AddBreakpointMessage& nmsg = (const AddBreakpointMessage&)aMsg; - data.AppendPrintf( - "Kind %s, Script %d, Offset %d, Frame %d", - nmsg.mPosition.KindString(), (int)nmsg.mPosition.mScript, - (int)nmsg.mPosition.mOffset, (int)nmsg.mPosition.mFrameIndex); - break; - } - case MessageType::DebuggerRequest: { - const DebuggerRequestMessage& nmsg = (const DebuggerRequestMessage&)aMsg; + case MessageType::ManifestStart: { + const ManifestStartMessage& nmsg = (const ManifestStartMessage&)aMsg; data = NS_ConvertUTF16toUTF8( nsDependentSubstring(nmsg.Buffer(), nmsg.BufferSize())); break; } - case MessageType::DebuggerResponse: { - const DebuggerResponseMessage& nmsg = - (const DebuggerResponseMessage&)aMsg; + case MessageType::ManifestFinished: { + const ManifestFinishedMessage& nmsg = + (const ManifestFinishedMessage&)aMsg; data = NS_ConvertUTF16toUTF8( nsDependentSubstring(nmsg.Buffer(), nmsg.BufferSize())); break; } - case MessageType::SetIsActive: { - const SetIsActiveMessage& nmsg = (const SetIsActiveMessage&)aMsg; - data.AppendPrintf("%d", nmsg.mActive); - break; - } - case MessageType::SetSaveCheckpoint: { - const SetSaveCheckpointMessage& nmsg = - (const SetSaveCheckpointMessage&)aMsg; - data.AppendPrintf("Id %d, Save %d", (int)nmsg.mCheckpoint, nmsg.mSave); - break; - } default: break; } diff --git a/toolkit/recordreplay/ipc/Channel.h b/toolkit/recordreplay/ipc/Channel.h index ae3f70546e71..5b882201978d 100644 --- a/toolkit/recordreplay/ipc/Channel.h +++ b/toolkit/recordreplay/ipc/Channel.h @@ -42,13 +42,6 @@ namespace recordreplay { // messages from being lost when they are sent from the middleman as the // replaying process rewinds itself. A few exceptions to this rule are noted // below. -// -// Some additional synchronization is needed between different child processes: -// replaying processes can read from the same file which a recording process is -// writing to. While it is ok for a replaying process to read from the file -// while the recording process is appending new chunks to it (see File.cpp), -// all replaying processes must be paused when the recording process is -// flushing a new index to the file. #define ForEachMessageType(_Macro) \ /* Messages sent from the middleman to the child process. */ \ @@ -64,56 +57,20 @@ namespace recordreplay { /* process to crash. */ \ _Macro(Terminate) \ \ - /* Flush the current recording to disk. */ \ - _Macro(FlushRecording) \ - \ /* Poke a child that is recording to create an artificial checkpoint, rather than */ \ /* (potentially) idling indefinitely. This has no effect on a replaying process. */ \ _Macro(CreateCheckpoint) \ \ - /* Debugger JSON messages are initially sent from the parent. The child unpauses */ \ - /* after receiving the message and will pause after it sends a DebuggerResponse. */ \ - _Macro(DebuggerRequest) \ - \ - /* Add a breakpoint position to stop at. Because a single entry point is used for */ \ - /* calling into the ReplayDebugger after pausing, the set of breakpoints is simply */ \ - /* a set of positions at which the child process should pause and send a */ \ - /* HitExecutionPoint message. */ \ - _Macro(AddBreakpoint) \ - \ - /* Clear all installed breakpoints. */ \ - _Macro(ClearBreakpoints) \ - \ - /* Unpause the child and play execution either to the next point when a */ \ - /* breakpoint is hit, or to the next checkpoint. Resumption may be either */ \ - /* forward or backward. */ \ - _Macro(Resume) \ - \ - /* Rewind to a particular saved checkpoint in the past. */ \ - _Macro(RestoreCheckpoint) \ - \ - /* Run forward to a particular execution point between the current checkpoint */ \ - /* and the next one. */ \ - _Macro(RunToPoint) \ - \ - /* Notify the child whether it is the active child and should send paint and similar */ \ - /* messages to the middleman. */ \ - _Macro(SetIsActive) \ - \ - /* Set whether to perform intentional crashes, for testing. */ \ - _Macro(SetAllowIntentionalCrashes) \ - \ - /* Set whether to save a particular checkpoint. */ \ - _Macro(SetSaveCheckpoint) \ + /* Unpause the child and perform a debugger-defined operation. */ \ + _Macro(ManifestStart) \ \ /* Respond to a MiddlemanCallRequest message. */ \ _Macro(MiddlemanCallResponse) \ \ /* Messages sent from the child process to the middleman. */ \ \ - /* Sent in response to a FlushRecording, telling the middleman that the flush */ \ - /* has finished. */ \ - _Macro(RecordingFlushed) \ + /* Pause after executing a manifest, specifying its response. */ \ + _Macro(ManifestFinished) \ \ /* A critical error occurred and execution cannot continue. The child will */ \ /* stop executing after sending this message and will wait to be terminated. */ \ @@ -127,21 +84,12 @@ namespace recordreplay { /* The child's graphics were repainted. */ \ _Macro(Paint) \ \ - /* Notify the middleman that the child has hit an execution point and paused. */ \ - _Macro(HitExecutionPoint) \ - \ - /* Send a response to a DebuggerRequest message. */ \ - _Macro(DebuggerResponse) \ - \ /* Call a system function from the middleman process which the child has */ \ /* encountered after diverging from the recording. */ \ _Macro(MiddlemanCallRequest) \ \ /* Reset all information generated by previous MiddlemanCallRequest messages. */ \ - _Macro(ResetMiddlemanCalls) \ - \ - /* Notify that the 'AlwaysMarkMajorCheckpoints' directive was invoked. */ \ - _Macro(AlwaysMarkMajorCheckpoints) + _Macro(ResetMiddlemanCalls) enum class MessageType { #define DefineEnum(Kind) Kind, @@ -195,7 +143,8 @@ struct Message { return mType == MessageType::CreateCheckpoint || mType == MessageType::SetDebuggerRunsInMiddleman || mType == MessageType::MiddlemanCallResponse || - mType == MessageType::Terminate; + mType == MessageType::Terminate || + mType == MessageType::Introduction; } protected: @@ -275,7 +224,6 @@ typedef EmptyMessage SetDebuggerRunsInMiddlemanMessage; typedef EmptyMessage TerminateMessage; typedef EmptyMessage CreateCheckpointMessage; -typedef EmptyMessage FlushRecordingMessage; template struct JSONMessage : public Message { @@ -293,75 +241,8 @@ struct JSONMessage : public Message { } }; -typedef JSONMessage DebuggerRequestMessage; -typedef JSONMessage DebuggerResponseMessage; - -struct AddBreakpointMessage : public Message { - js::BreakpointPosition mPosition; - - explicit AddBreakpointMessage(const js::BreakpointPosition& aPosition) - : Message(MessageType::AddBreakpoint, sizeof(*this)), - mPosition(aPosition) {} -}; - -typedef EmptyMessage ClearBreakpointsMessage; - -struct ResumeMessage : public Message { - // Whether to travel forwards or backwards. - bool mForward; - - explicit ResumeMessage(bool aForward) - : Message(MessageType::Resume, sizeof(*this)), mForward(aForward) {} -}; - -struct RestoreCheckpointMessage : public Message { - // The checkpoint to restore. - size_t mCheckpoint; - - explicit RestoreCheckpointMessage(size_t aCheckpoint) - : Message(MessageType::RestoreCheckpoint, sizeof(*this)), - mCheckpoint(aCheckpoint) {} -}; - -struct RunToPointMessage : public Message { - // The target execution point. - js::ExecutionPoint mTarget; - - explicit RunToPointMessage(const js::ExecutionPoint& aTarget) - : Message(MessageType::RunToPoint, sizeof(*this)), mTarget(aTarget) {} -}; - -struct SetIsActiveMessage : public Message { - // Whether this is the active child process (see ParentIPC.cpp). - bool mActive; - - explicit SetIsActiveMessage(bool aActive) - : Message(MessageType::SetIsActive, sizeof(*this)), mActive(aActive) {} -}; - -struct SetAllowIntentionalCrashesMessage : public Message { - // Whether to allow intentional crashes in the future or not. - bool mAllowed; - - explicit SetAllowIntentionalCrashesMessage(bool aAllowed) - : Message(MessageType::SetAllowIntentionalCrashes, sizeof(*this)), - mAllowed(aAllowed) {} -}; - -struct SetSaveCheckpointMessage : public Message { - // The checkpoint in question. - size_t mCheckpoint; - - // Whether to save this checkpoint whenever it is encountered. - bool mSave; - - SetSaveCheckpointMessage(size_t aCheckpoint, bool aSave) - : Message(MessageType::SetSaveCheckpoint, sizeof(*this)), - mCheckpoint(aCheckpoint), - mSave(aSave) {} -}; - -typedef EmptyMessage RecordingFlushedMessage; +typedef JSONMessage ManifestStartMessage; +typedef JSONMessage ManifestFinishedMessage; struct FatalErrorMessage : public Message { explicit FatalErrorMessage(uint32_t aSize) @@ -391,29 +272,6 @@ struct PaintMessage : public Message { mHeight(aHeight) {} }; -struct HitExecutionPointMessage : public Message { - // The point the child paused at. - js::ExecutionPoint mPoint; - - // Whether the pause occurred due to hitting the end of the recording. - bool mRecordingEndpoint; - - // The amount of non-idle time taken to get to this pause from the last time - // the child paused. - double mDurationMicroseconds; - - HitExecutionPointMessage(const js::ExecutionPoint& aPoint, - bool aRecordingEndpoint, - double aDurationMicroseconds) - : Message(MessageType::HitExecutionPoint, sizeof(*this)), - mPoint(aPoint), - mRecordingEndpoint(aRecordingEndpoint), - mDurationMicroseconds(aDurationMicroseconds) {} -}; - -typedef EmptyMessage - AlwaysMarkMajorCheckpointsMessage; - template struct BinaryMessage : public Message { explicit BinaryMessage(uint32_t aSize) : Message(Type, aSize) {} diff --git a/toolkit/recordreplay/ipc/ChildIPC.cpp b/toolkit/recordreplay/ipc/ChildIPC.cpp index 97240e5c56d4..17baec10303d 100644 --- a/toolkit/recordreplay/ipc/ChildIPC.cpp +++ b/toolkit/recordreplay/ipc/ChildIPC.cpp @@ -77,9 +77,11 @@ static void ChannelMessageHandler(Message::UniquePtr aMsg) { switch (aMsg->mType) { case MessageType::Introduction: { + MonitorAutoLock lock(*gMonitor); MOZ_RELEASE_ASSERT(!gIntroductionMessage); gIntroductionMessage.reset( static_cast(aMsg.release())); + gMonitor->NotifyAll(); break; } case MessageType::CreateCheckpoint: { @@ -87,7 +89,7 @@ static void ChannelMessageHandler(Message::UniquePtr aMsg) { // Ignore requests to create checkpoints before we have reached the first // paint and finished initializing. - if (navigation::IsInitialized()) { + if (js::IsInitialized()) { uint8_t data = 0; DirectWrite(gCheckpointWriteFd, &data, 1); } @@ -113,66 +115,14 @@ static void ChannelMessageHandler(Message::UniquePtr aMsg) { } break; } - case MessageType::SetIsActive: { - const SetIsActiveMessage& nmsg = (const SetIsActiveMessage&)*aMsg; - PauseMainThreadAndInvokeCallback( - [=]() { SetIsActiveChild(nmsg.mActive); }); - break; - } - case MessageType::SetAllowIntentionalCrashes: { - const SetAllowIntentionalCrashesMessage& nmsg = - (const SetAllowIntentionalCrashesMessage&)*aMsg; - PauseMainThreadAndInvokeCallback( - [=]() { SetAllowIntentionalCrashes(nmsg.mAllowed); }); - break; - } - case MessageType::SetSaveCheckpoint: { - const SetSaveCheckpointMessage& nmsg = - (const SetSaveCheckpointMessage&)*aMsg; - PauseMainThreadAndInvokeCallback( - [=]() { SetSaveCheckpoint(nmsg.mCheckpoint, nmsg.mSave); }); - break; - } - case MessageType::FlushRecording: { - PauseMainThreadAndInvokeCallback(FlushRecording); - break; - } - case MessageType::DebuggerRequest: { - const DebuggerRequestMessage& nmsg = (const DebuggerRequestMessage&)*aMsg; + case MessageType::ManifestStart: { + const ManifestStartMessage& nmsg = (const ManifestStartMessage&)*aMsg; js::CharBuffer* buf = new js::CharBuffer(); buf->append(nmsg.Buffer(), nmsg.BufferSize()); - PauseMainThreadAndInvokeCallback( - [=]() { navigation::DebuggerRequest(buf); }); - break; - } - case MessageType::AddBreakpoint: { - const AddBreakpointMessage& nmsg = (const AddBreakpointMessage&)*aMsg; - PauseMainThreadAndInvokeCallback( - [=]() { navigation::AddBreakpoint(nmsg.mPosition); }); - break; - } - case MessageType::ClearBreakpoints: { - PauseMainThreadAndInvokeCallback( - [=]() { navigation::ClearBreakpoints(); }); - break; - } - case MessageType::Resume: { - const ResumeMessage& nmsg = (const ResumeMessage&)*aMsg; - PauseMainThreadAndInvokeCallback( - [=]() { navigation::Resume(nmsg.mForward); }); - break; - } - case MessageType::RestoreCheckpoint: { - const RestoreCheckpointMessage& nmsg = - (const RestoreCheckpointMessage&)*aMsg; - PauseMainThreadAndInvokeCallback( - [=]() { navigation::RestoreCheckpoint(nmsg.mCheckpoint); }); - break; - } - case MessageType::RunToPoint: { - const RunToPointMessage& nmsg = (const RunToPointMessage&)*aMsg; - PauseMainThreadAndInvokeCallback( - [=]() { navigation::RunToPoint(nmsg.mTarget); }); + PauseMainThreadAndInvokeCallback([=]() { + js::ManifestStart(*buf); + delete buf; + }); break; } case MessageType::MiddlemanCallResponse: { @@ -201,8 +151,7 @@ static void ListenForCheckpointThreadMain(void*) { ssize_t rv = HANDLE_EINTR(read(gCheckpointReadFd, &data, 1)); if (rv > 0) { NS_DispatchToMainThread(NewRunnableFunction("NewCheckpoint", - NewCheckpoint, - /* aTemporary = */ false)); + NewCheckpoint)); } else { MOZ_RELEASE_ASSERT(errno == EIO); MOZ_RELEASE_ASSERT(HasDivergedFromRecording()); @@ -292,16 +241,20 @@ void InitRecordingOrReplayingProcess(int* aArgc, char*** aArgv) { pt.reset(); - // We are ready to receive initialization messages from the middleman, pause - // so they can be sent. - HitExecutionPoint(js::ExecutionPoint(), /* aRecordingEndpoint = */ false); - // If we failed to initialize then report it to the user. if (gInitializationFailureMessage) { ReportFatalError(Nothing(), "%s", gInitializationFailureMessage); Unreachable(); } + // Wait for the parent to send us the introduction message. + { + MonitorAutoLock lock(*gMonitor); + while (!gIntroductionMessage) { + gMonitor->Wait(); + } + } + // Process the introduction message to fill in arguments. MOZ_RELEASE_ASSERT(gParentArgv.empty()); @@ -344,7 +297,7 @@ bool DebuggerRunsInMiddleman() { void CreateCheckpoint() { if (!HasDivergedFromRecording()) { - NewCheckpoint(/* aTemporary = */ false); + NewCheckpoint(); } } @@ -392,16 +345,6 @@ void ReportFatalError(const Maybe& aMinidump, const char* aFormat, Thread::WaitForeverNoIdle(); } -void NotifyFlushedRecording() { - gChannel->SendMessage(RecordingFlushedMessage()); -} - -void NotifyAlwaysMarkMajorCheckpoints() { - if (IsActiveChild()) { - gChannel->SendMessage(AlwaysMarkMajorCheckpointsMessage()); - } -} - /////////////////////////////////////////////////////////////////////////////// // Vsyncs /////////////////////////////////////////////////////////////////////////////// @@ -543,10 +486,9 @@ static void PaintFromMainThread() { // operating on the draw target buffer. MOZ_RELEASE_ASSERT(!gNumPendingPaints); - if (IsActiveChild() && navigation::ShouldSendPaintMessage() && - gDrawTargetBuffer) { + if (IsMainChild() && gDrawTargetBuffer) { memcpy(gGraphicsShmem, gDrawTargetBuffer, gDrawTargetBufferSize); - gChannel->SendMessage(PaintMessage(navigation::LastNormalCheckpoint(), + gChannel->SendMessage(PaintMessage(GetLastCheckpoint(), gPaintWidth, gPaintHeight)); } } @@ -630,55 +572,18 @@ bool CurrentRepaintCannotFail() { return gRepainting && !gAllowRepaintFailures; } -/////////////////////////////////////////////////////////////////////////////// -// Checkpoint Messages -/////////////////////////////////////////////////////////////////////////////// - -// The time when the last HitExecutionPoint message was sent. -static double gLastPauseTime; - -// When recording and we are idle, the time when we became idle. -static double gIdleTimeStart; - -void BeginIdleTime() { - MOZ_RELEASE_ASSERT(IsRecording() && NS_IsMainThread() && !gIdleTimeStart); - gIdleTimeStart = CurrentTime(); -} - -void EndIdleTime() { - MOZ_RELEASE_ASSERT(IsRecording() && NS_IsMainThread() && gIdleTimeStart); - - // Erase the idle time from our measurements by advancing the last checkpoint - // time. - gLastPauseTime += CurrentTime() - gIdleTimeStart; - gIdleTimeStart = 0; -} - -void HitExecutionPoint(const js::ExecutionPoint& aPoint, - bool aRecordingEndpoint) { - MOZ_RELEASE_ASSERT(NS_IsMainThread()); - double time = CurrentTime(); - PauseMainThreadAndInvokeCallback([=]() { - double duration = 0; - if (gLastPauseTime) { - duration = time - gLastPauseTime; - MOZ_RELEASE_ASSERT(duration > 0); - } - gChannel->SendMessage( - HitExecutionPointMessage(aPoint, aRecordingEndpoint, duration)); - }); - gLastPauseTime = time; -} - /////////////////////////////////////////////////////////////////////////////// // Message Helpers /////////////////////////////////////////////////////////////////////////////// -void RespondToRequest(const js::CharBuffer& aBuffer) { - DebuggerResponseMessage* msg = - DebuggerResponseMessage::New(aBuffer.begin(), aBuffer.length()); - gChannel->SendMessage(std::move(*msg)); - free(msg); +void ManifestFinished(const js::CharBuffer& aBuffer) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + ManifestFinishedMessage* msg = + ManifestFinishedMessage::New(aBuffer.begin(), aBuffer.length()); + PauseMainThreadAndInvokeCallback([=]() { + gChannel->SendMessage(std::move(*msg)); + free(msg); + }); } void SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize, diff --git a/toolkit/recordreplay/ipc/ChildInternal.h b/toolkit/recordreplay/ipc/ChildInternal.h index 9878b2c97bfe..e99fa84b7b5f 100644 --- a/toolkit/recordreplay/ipc/ChildInternal.h +++ b/toolkit/recordreplay/ipc/ChildInternal.h @@ -13,86 +13,13 @@ #include "MiddlemanCall.h" #include "Monitor.h" -namespace mozilla { -namespace recordreplay { - // This file has internal definitions for communication between the main // record/replay infrastructure and child side IPC code. -// The navigation namespace has definitions for managing breakpoints and all -// other state that persists across rewinds, and for keeping track of the -// precise execution position of the child process. The middleman will send the -// child process Resume messages to travel forward and backward, but it is up -// to the child process to keep track of the rewinding and resuming necessary -// to find the next or previous point where a breakpoint or checkpoint is hit. -namespace navigation { - -// Navigation state is initialized when the first checkpoint is reached. -bool IsInitialized(); - -// In a recording process, get the current execution point, aka the endpoint -// of the recording. -js::ExecutionPoint GetRecordingEndpoint(); - -// In a replaying process, set the recording endpoint. |index| is used to -// differentiate different endpoints that have been sequentially written to -// the recording file as it has been flushed. -void SetRecordingEndpoint(size_t aIndex, const js::ExecutionPoint& aEndpoint); - -// Save temporary checkpoints at all opportunities during navigation. -void AlwaysSaveTemporaryCheckpoints(); - -// Process incoming requests from the middleman. -void DebuggerRequest(js::CharBuffer* aBuffer); -void AddBreakpoint(const js::BreakpointPosition& aPosition); -void ClearBreakpoints(); -void Resume(bool aForward); -void RestoreCheckpoint(size_t aId); -void RunToPoint(const js::ExecutionPoint& aPoint); - -// Attempt to diverge from the recording so that new recorded events cause -// the process to rewind. Returns false if the divergence failed: either we -// can't rewind, or already diverged here and then had an unhandled divergence. -bool MaybeDivergeFromRecording(); - -// Notify navigation that a position was hit. -void PositionHit(const js::BreakpointPosition& aPosition); - -// Get an execution point for hitting the specified position right now. -js::ExecutionPoint CurrentExecutionPoint( - const Maybe& aPosition); - -// Convert an identifier from NewTimeWarpTarget() which we have seen while -// executing into an ExecutionPoint. -js::ExecutionPoint TimeWarpTargetExecutionPoint(ProgressCounter aTarget); - -// Synchronously paint the current contents into the graphics shared memory -// object, returning the size of the painted area via aWidth/aHeight. -void Repaint(size_t* aWidth, size_t* aHeight); - -// Called when running forward, immediately before hitting a normal or -// temporary checkpoint. -void BeforeCheckpoint(); - -// Called immediately after hitting a normal or temporary checkpoint, either -// when running forward or immediately after rewinding. -void AfterCheckpoint(const CheckpointId& aCheckpoint); - -// Get the ID of the last normal checkpoint. -size_t LastNormalCheckpoint(); - -// Whether to send a paint message for the last normal checkpoint reached. -bool ShouldSendPaintMessage(); - -} // namespace navigation - +namespace mozilla { +namespace recordreplay { namespace child { -// IPC activity that can be triggered by navigation. -void RespondToRequest(const js::CharBuffer& aBuffer); -void HitExecutionPoint(const js::ExecutionPoint& aPoint, - bool aRecordingEndpoint); - // Optional information about a crash that occurred. If not provided to // ReportFatalError, the current thread will be treated as crashed. struct MinidumpInfo { @@ -115,19 +42,12 @@ void ReportFatalError(const Maybe& aMinidumpInfo, // Monitor used for various synchronization tasks. extern Monitor* gMonitor; -// Notify the middleman that the recording was flushed. -void NotifyFlushedRecording(); - -// Notify the middleman about an AlwaysMarkMajorCheckpoints directive. -void NotifyAlwaysMarkMajorCheckpoints(); - -// Mark a time span when the main thread is idle. -void BeginIdleTime(); -void EndIdleTime(); - // Whether the middleman runs developer tools server code. bool DebuggerRunsInMiddleman(); +// Notify the middleman that the last manifest was finished. +void ManifestFinished(const js::CharBuffer& aResponse); + // Send messages operating on middleman calls. void SendMiddlemanCallRequest(const char* aInputData, size_t aInputSize, InfallibleVector* aOutputData); @@ -138,7 +58,6 @@ void SendResetMiddlemanCalls(); bool CurrentRepaintCannotFail(); } // namespace child - } // namespace recordreplay } // namespace mozilla diff --git a/toolkit/recordreplay/ipc/ChildNavigation.cpp b/toolkit/recordreplay/ipc/ChildNavigation.cpp deleted file mode 100644 index c40123bcd5f6..000000000000 --- a/toolkit/recordreplay/ipc/ChildNavigation.cpp +++ /dev/null @@ -1,1195 +0,0 @@ -/* -*- 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 "ChildInternal.h" - -#include "ProcessRecordReplay.h" - -namespace mozilla { -namespace recordreplay { -namespace navigation { - -typedef js::BreakpointPosition BreakpointPosition; -typedef js::ExecutionPoint ExecutionPoint; - -/////////////////////////////////////////////////////////////////////////////// -// Navigation State -/////////////////////////////////////////////////////////////////////////////// - -// The navigation state of a recording/replaying process describes where the -// process currently is and what it is doing in order to respond to messages -// from the middleman process. -// -// At all times, the navigation state will be in exactly one of the following -// phases: -// -// - Paused: The process is paused somewhere. -// - Forward: The process is running forward and scanning for breakpoint hits. -// - ReachBreakpoint: The process is running forward from a checkpoint to a -// particular execution point before the next checkpoint. -// - FindLastHit: The process is running forward and keeping track of the last -// point a breakpoint was hit within an execution region. -// -// This file manages data associated with each of these phases and the -// transitions that occur between them as the process executes or new -// messages are received from the middleman. - -typedef AllocPolicy UntrackedAllocPolicy; - -// Abstract class for where we are at in the navigation state machine. -// Each subclass has a single instance contained in NavigationState (see below) -// and it and all its data are allocated using untracked memory that is not -// affected by restoring earlier checkpoints. -class NavigationPhase { - // All virtual members should only be accessed through NavigationState. - friend class NavigationState; - - private: - MOZ_NORETURN void Unsupported(const char* aOperation) { - nsAutoCString str; - ToString(str); - - Print("Operation %s not supported: %s\n", aOperation, str.get()); - MOZ_CRASH("Unsupported navigation operation"); - } - - public: - virtual void ToString(nsAutoCString& aStr) = 0; - - // The process has just reached or rewound to a checkpoint. - virtual void AfterCheckpoint(const CheckpointId& aCheckpoint) { - Unsupported("AfterCheckpoint"); - } - - // Called when some position with an installed handler has been reached. - virtual void PositionHit(const ExecutionPoint& aPoint) { - Unsupported("PositionHit"); - } - - // Called after receiving a resume command from the middleman. - virtual void Resume(bool aForward) { Unsupported("Resume"); } - - // Called after the middleman tells us to rewind to a specific checkpoint. - virtual void RestoreCheckpoint(size_t aCheckpoint) { - Unsupported("RestoreCheckpoint"); - } - - // Called after the middleman tells us to run forward to a specific point. - virtual void RunToPoint(const ExecutionPoint& aTarget) { - Unsupported("RunToPoint"); - } - - // Process an incoming debugger request from the middleman. - virtual void HandleDebuggerRequest(js::CharBuffer* aRequestBuffer) { - Unsupported("HandleDebuggerRequest"); - } - - // Called when a debugger request wants to try an operation that may - // trigger an unhandled divergence from the recording. - virtual bool MaybeDivergeFromRecording() { - Unsupported("MaybeDivergeFromRecording"); - } - - // Get the current execution point. - virtual ExecutionPoint CurrentExecutionPoint() { - Unsupported("CurrentExecutionPoint"); - } - - // Called when execution reaches the endpoint of the recording. - virtual void HitRecordingEndpoint(const ExecutionPoint& aPoint) { - Unsupported("HitRecordingEndpoint"); - } - - // Called when a paint has occurred for the last normal checkpoint. - virtual bool ShouldSendPaintMessage() { - Unsupported("ShouldSendPaintMessage"); - } -}; - -// Information about a debugger request sent by the middleman. -struct RequestInfo { - // JSON contents for the request and response. - InfallibleVector mRequestBuffer; - InfallibleVector mResponseBuffer; - - // Whether processing this request triggered an unhandled divergence. - bool mUnhandledDivergence; - - RequestInfo() : mUnhandledDivergence(false) {} - - RequestInfo(const RequestInfo& o) - : mUnhandledDivergence(o.mUnhandledDivergence) { - mRequestBuffer.append(o.mRequestBuffer.begin(), o.mRequestBuffer.length()); - mResponseBuffer.append(o.mResponseBuffer.begin(), - o.mResponseBuffer.length()); - } -}; -typedef InfallibleVector - UntrackedRequestVector; - -// Phase when the replaying process is paused. -class PausedPhase final : public NavigationPhase { - // Location of the pause. - ExecutionPoint mPoint; - - // Whether we are paused at the end of the recording. - bool mRecordingEndpoint; - - // All debugger requests we have seen while paused here. - UntrackedRequestVector mRequests; - - // Index of the request currently being processed. Normally this is the - // last entry in |mRequests|, though may be earlier if we are recovering - // from an unhandled divergence. - size_t mRequestIndex; - - // Whether we have saved a temporary checkpoint. - bool mSavedTemporaryCheckpoint; - - // Whether we had to restore a checkpoint to deal with an unhandled - // recording divergence, and haven't finished rehandling old requests. - bool mRecoveringFromDivergence; - - // Set when we were told to resume forward and need to clean up our state. - bool mResumeForward; - - public: - void Enter(const ExecutionPoint& aPoint, bool aRewind = false, - bool aRecordingEndpoint = false); - - void ToString(nsAutoCString& aStr) override { - aStr.AppendPrintf("Paused RecoveringFromDivergence %d", - mRecoveringFromDivergence); - } - - void AfterCheckpoint(const CheckpointId& aCheckpoint) override; - void PositionHit(const ExecutionPoint& aPoint) override; - void Resume(bool aForward) override; - void RestoreCheckpoint(size_t aCheckpoint) override; - void RunToPoint(const ExecutionPoint& aTarget) override; - void HandleDebuggerRequest(js::CharBuffer* aRequestBuffer) override; - bool MaybeDivergeFromRecording() override; - ExecutionPoint CurrentExecutionPoint() override; - - bool EnsureTemporaryCheckpoint(); -}; - -// Phase when execution is proceeding forwards in search of breakpoint hits. -class ForwardPhase final : public NavigationPhase { - // Some execution point in the recent past. There are no checkpoints or - // breakpoint hits between this point and the current point of execution. - ExecutionPoint mPoint; - - public: - void Enter(const ExecutionPoint& aPoint); - - void ToString(nsAutoCString& aStr) override { aStr.AppendPrintf("Forward"); } - - void AfterCheckpoint(const CheckpointId& aCheckpoint) override; - void PositionHit(const ExecutionPoint& aPoint) override; - void HitRecordingEndpoint(const ExecutionPoint& aPoint) override; - bool ShouldSendPaintMessage() override; -}; - -// Phase when the replaying process is running forward from a checkpoint to a -// breakpoint at a particular execution point. -class ReachBreakpointPhase final : public NavigationPhase { - private: - // Where to start running from. - CheckpointId mStart; - - // The point we are running to. - ExecutionPoint mPoint; - - // Point at which to decide whether to save a temporary checkpoint. - Maybe mTemporaryCheckpoint; - - // Whether we have saved a temporary checkpoint at the specified point. - bool mSavedTemporaryCheckpoint; - - // The time at which we started running forward from the initial - // checkpoint, in microseconds. - double mStartTime; - - public: - void Enter(const CheckpointId& aStart, bool aRewind, - const ExecutionPoint& aPoint, - const Maybe& aTemporaryCheckpoint); - - void ToString(nsAutoCString& aStr) override { - aStr.AppendPrintf("ReachBreakpoint: "); - mPoint.ToString(aStr); - if (mTemporaryCheckpoint.isSome()) { - aStr.AppendPrintf(" TemporaryCheckpoint: "); - mTemporaryCheckpoint.ref().ToString(aStr); - } - } - - void AfterCheckpoint(const CheckpointId& aCheckpoint) override; - void PositionHit(const ExecutionPoint& aPoint) override; - bool ShouldSendPaintMessage() override; -}; - -// Phase when the replaying process is searching forward from a checkpoint to -// find the last point a breakpoint is hit before reaching an execution point. -class FindLastHitPhase final : public NavigationPhase { - // Where we started searching from. - CheckpointId mStart; - - // Endpoint of the search. - ExecutionPoint mEnd; - - // Whether the endpoint itself is considered to be part of the search space. - bool mIncludeEnd; - - // Counter that increases as we run forward, for ordering hits. - size_t mCounter; - - // All positions we are interested in hits for, including all breakpoint - // positions (and possibly other positions). - struct TrackedPosition { - BreakpointPosition mPosition; - - // The last time this was hit so far, or invalid. - ExecutionPoint mLastHit; - - // The value of the counter when the last hit occurred. - size_t mLastHitCount; - - explicit TrackedPosition(const BreakpointPosition& aPosition) - : mPosition(aPosition), mLastHitCount(0) {} - }; - InfallibleVector mTrackedPositions; - - const TrackedPosition& FindTrackedPosition(const BreakpointPosition& aPos); - void CheckForRegionEnd(const ExecutionPoint& aPoint); - void OnRegionEnd(); - - public: - // Note: this always rewinds. - void Enter(const CheckpointId& aStart, const ExecutionPoint& aEnd, - bool aIncludeEnd); - - void ToString(nsAutoCString& aStr) override { - aStr.AppendPrintf("FindLastHit #%zu:%zu", mStart.mNormal, - mStart.mTemporary); - mEnd.ToString(aStr); - } - - void AfterCheckpoint(const CheckpointId& aCheckpoint) override; - void PositionHit(const ExecutionPoint& aPoint) override; - void HitRecordingEndpoint(const ExecutionPoint& aPoint) override; - bool ShouldSendPaintMessage() override; -}; - -// Structure which manages state about the breakpoints in existence and about -// how the process is being navigated through. This is allocated in untracked -// memory and its contents will not change when restoring an earlier -// checkpoint. -class NavigationState { - // When replaying, the last known recording endpoint. There may be other, - // later endpoints we haven't been informed about. - ExecutionPoint mRecordingEndpoint; - size_t mRecordingEndpointIndex; - - // The last checkpoint we ran forward or rewound to. - CheckpointId mLastCheckpoint; - - // The locations of all temporary checkpoints we have saved. Temporary - // checkpoints are taken immediately prior to reaching these points. - InfallibleVector - mTemporaryCheckpoints; - - public: - // All the currently installed breakpoints. - InfallibleVector mBreakpoints; - - CheckpointId LastCheckpoint() { return mLastCheckpoint; } - - // The current phase of the process. - NavigationPhase* mPhase; - - void SetPhase(NavigationPhase* phase) { - mPhase = phase; - - if (SpewEnabled()) { - nsAutoCString str; - mPhase->ToString(str); - - PrintSpew("SetNavigationPhase %s\n", str.get()); - } - } - - PausedPhase mPausedPhase; - ForwardPhase mForwardPhase; - ReachBreakpointPhase mReachBreakpointPhase; - FindLastHitPhase mFindLastHitPhase; - - // For testing, specify that temporary checkpoints should be taken regardless - // of how much time has elapsed. - bool mAlwaysSaveTemporaryCheckpoints; - - // Progress counts for all checkpoints that have been encountered. - InfallibleVector - mCheckpointProgress; - - // Note: NavigationState is initially zeroed. - NavigationState() : mPhase(&mForwardPhase) { - if (IsReplaying()) { - // The recording must include everything up to the first - // checkpoint. After that point we will ask the record/replay - // system to notify us about any further endpoints. - mRecordingEndpoint = ExecutionPoint(CheckpointId::First, 0); - } - mCheckpointProgress.append(0); - } - - void AfterCheckpoint(const CheckpointId& aCheckpoint) { - mLastCheckpoint = aCheckpoint; - - // Forget any temporary checkpoints we just rewound past, or made - // obsolete by reaching the next normal checkpoint. - while (mTemporaryCheckpoints.length() > aCheckpoint.mTemporary) { - mTemporaryCheckpoints.popBack(); - } - - // Update the progress counter for each normal checkpoint. - if (!aCheckpoint.mTemporary) { - ProgressCounter progress = *ExecutionProgressCounter(); - if (aCheckpoint.mNormal < mCheckpointProgress.length()) { - MOZ_RELEASE_ASSERT(progress == - mCheckpointProgress[aCheckpoint.mNormal]); - } else { - MOZ_RELEASE_ASSERT(aCheckpoint.mNormal == mCheckpointProgress.length()); - mCheckpointProgress.append(progress); - } - } - - mPhase->AfterCheckpoint(aCheckpoint); - - // Make sure we don't run past the end of the recording. - if (!aCheckpoint.mTemporary) { - CheckForRecordingEndpoint(CheckpointExecutionPoint(aCheckpoint.mNormal)); - } - - MOZ_RELEASE_ASSERT(IsRecording() || - aCheckpoint.mNormal <= mRecordingEndpoint.mCheckpoint); - if (aCheckpoint.mNormal == mRecordingEndpoint.mCheckpoint && - mRecordingEndpoint.HasPosition()) { - js::EnsurePositionHandler(mRecordingEndpoint.mPosition); - } - } - - void PositionHit(const ExecutionPoint& aPoint) { - mPhase->PositionHit(aPoint); - CheckForRecordingEndpoint(aPoint); - } - - void Resume(bool aForward) { mPhase->Resume(aForward); } - - void RestoreCheckpoint(size_t aCheckpoint) { - mPhase->RestoreCheckpoint(aCheckpoint); - } - - void RunToPoint(const ExecutionPoint& aTarget) { - mPhase->RunToPoint(aTarget); - } - - void HandleDebuggerRequest(js::CharBuffer* aRequestBuffer) { - mPhase->HandleDebuggerRequest(aRequestBuffer); - } - - bool MaybeDivergeFromRecording() { - return mPhase->MaybeDivergeFromRecording(); - } - - ExecutionPoint CurrentExecutionPoint() { - return mPhase->CurrentExecutionPoint(); - } - - bool ShouldSendPaintMessage() { return mPhase->ShouldSendPaintMessage(); } - - void SetRecordingEndpoint(size_t aIndex, const ExecutionPoint& aEndpoint) { - // Ignore endpoints older than the last one we know about. - if (aIndex <= mRecordingEndpointIndex) { - return; - } - MOZ_RELEASE_ASSERT(mRecordingEndpoint.mCheckpoint <= aEndpoint.mCheckpoint); - mRecordingEndpointIndex = aIndex; - mRecordingEndpoint = aEndpoint; - if (aEndpoint.HasPosition()) { - js::EnsurePositionHandler(aEndpoint.mPosition); - } - } - - void CheckForRecordingEndpoint(const ExecutionPoint& aPoint) { - while (aPoint == mRecordingEndpoint) { - // The recording ended after the checkpoint, but maybe there is - // another, later endpoint now. This may call back into - // setRecordingEndpoint and notify us there is more recording data - // available. - if (!recordreplay::HitRecordingEndpoint()) { - mPhase->HitRecordingEndpoint(mRecordingEndpoint); - } - } - } - - ExecutionPoint LastRecordingEndpoint() { - // Get the last recording endpoint in the recording file. - while (recordreplay::HitRecordingEndpoint()) { - } - return mRecordingEndpoint; - } - - bool SaveTemporaryCheckpoint(const ExecutionPoint& aPoint) { - MOZ_RELEASE_ASSERT(aPoint.mCheckpoint == mLastCheckpoint.mNormal); - mTemporaryCheckpoints.append(aPoint); - return NewCheckpoint(/* aTemporary = */ true); - } - - ExecutionPoint LastTemporaryCheckpointLocation() { - MOZ_RELEASE_ASSERT(!mTemporaryCheckpoints.empty()); - return mTemporaryCheckpoints.back(); - } - - ExecutionPoint CheckpointExecutionPoint(size_t aCheckpoint) { - MOZ_RELEASE_ASSERT(aCheckpoint < mCheckpointProgress.length()); - return ExecutionPoint(aCheckpoint, mCheckpointProgress[aCheckpoint]); - } -}; - -static NavigationState* gNavigation; - -// When searching backwards in the execution space, we need to ignore any -// temporary checkpoints associated with old normal checkpoints. We don't -// remember what execution points these old temporary checkpoints are -// associated with. -static CheckpointId SkipUnknownTemporaryCheckpoints( - const CheckpointId& aCheckpoint) { - CheckpointId rval = aCheckpoint; - while (rval.mTemporary && - rval.mNormal != gNavigation->LastCheckpoint().mNormal) { - rval = GetLastSavedCheckpointPriorTo(rval); - } - return rval; -} - -/////////////////////////////////////////////////////////////////////////////// -// Paused Phase -/////////////////////////////////////////////////////////////////////////////// - -static bool ThisProcessCanRewind() { return HasSavedCheckpoint(); } - -void PausedPhase::Enter(const ExecutionPoint& aPoint, bool aRewind, - bool aRecordingEndpoint) { - mPoint = aPoint; - mRecordingEndpoint = aRecordingEndpoint; - mRequests.clear(); - mRequestIndex = 0; - mSavedTemporaryCheckpoint = false; - mRecoveringFromDivergence = false; - mResumeForward = false; - - gNavigation->SetPhase(this); - - if (aRewind) { - MOZ_RELEASE_ASSERT(!aPoint.HasPosition()); - RestoreCheckpointAndResume(CheckpointId(aPoint.mCheckpoint)); - Unreachable(); - } - - child::HitExecutionPoint(aPoint, aRecordingEndpoint); -} - -void PausedPhase::AfterCheckpoint(const CheckpointId& aCheckpoint) { - MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence); - if (!aCheckpoint.mTemporary) { - // We just rewound here, and are now where we should pause. - MOZ_RELEASE_ASSERT( - mPoint == gNavigation->CheckpointExecutionPoint(aCheckpoint.mNormal)); - child::HitExecutionPoint(mPoint, mRecordingEndpoint); - } else { - // We just saved or restored the temporary checkpoint taken while - // processing debugger requests here. - MOZ_RELEASE_ASSERT(ThisProcessCanRewind()); - MOZ_RELEASE_ASSERT(mSavedTemporaryCheckpoint); - } -} - -void PausedPhase::PositionHit(const ExecutionPoint& aPoint) { - // Ignore positions hit while paused (we're probably doing an eval). -} - -void PausedPhase::Resume(bool aForward) { - MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence); - MOZ_RELEASE_ASSERT(!mResumeForward); - - if (aForward) { - // If we have saved any temporary checkpoint, we performed an operation - // that may have side effects. Clear these unwanted changes by restoring - // the temporary checkpoint we saved earlier. - if (mSavedTemporaryCheckpoint) { - mResumeForward = true; - RestoreCheckpointAndResume(gNavigation->LastCheckpoint()); - Unreachable(); - } - - js::ClearPausedState(); - - // Run forward from the current execution point. - gNavigation->mForwardPhase.Enter(mPoint); - return; - } - - // Search backwards in the execution space, from the last saved checkpoint to - // where we are paused. - CheckpointId start = GetLastSavedCheckpoint(); - - // Skip over any temporary checkpoint we saved. - if (mSavedTemporaryCheckpoint) { - start = GetLastSavedCheckpointPriorTo(start); - } - - // Skip to the previous saved checkpoint if we are paused at a checkpoint. - if (!mPoint.HasPosition() && start == CheckpointId(mPoint.mCheckpoint)) { - start = GetLastSavedCheckpointPriorTo(start); - } - - start = SkipUnknownTemporaryCheckpoints(start); - gNavigation->mFindLastHitPhase.Enter(start, mPoint, - /* aIncludeEnd = */ false); - Unreachable(); -} - -void PausedPhase::RestoreCheckpoint(size_t aCheckpoint) { - ExecutionPoint target = gNavigation->CheckpointExecutionPoint(aCheckpoint); - bool rewind = target != mPoint; - Enter(target, rewind, /* aRecordingEndpoint = */ false); -} - -void PausedPhase::RunToPoint(const ExecutionPoint& aTarget) { - // This may only be used when we are paused at a normal checkpoint. - MOZ_RELEASE_ASSERT(!mPoint.HasPosition()); - - ResumeExecution(); - - // If we saved a temporary checkpoint, we need to rewind to erase any side - // effects that have happened, as when resuming forward. - gNavigation->mReachBreakpointPhase.Enter( - gNavigation->LastCheckpoint(), /* aRewind = */ mSavedTemporaryCheckpoint, - aTarget, /* aTemporaryCheckpoint = */ Nothing()); -} - -void PausedPhase::HandleDebuggerRequest(js::CharBuffer* aRequestBuffer) { - MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence); - MOZ_RELEASE_ASSERT(!mResumeForward); - - mRequests.emplaceBack(); - size_t index = mRequests.length() - 1; - mRequests[index].mRequestBuffer.append(aRequestBuffer->begin(), - aRequestBuffer->length()); - - mRequestIndex = index; - - js::CharBuffer responseBuffer; - js::ProcessRequest(aRequestBuffer->begin(), aRequestBuffer->length(), - &responseBuffer); - - delete aRequestBuffer; - - if (gNavigation->mPhase != this) { - // We saved a temporary checkpoint by calling MaybeDivergeFromRecording - // within ProcessRequest, then restored it while scanning backwards. - ResumeExecution(); - return; - } - - if (!mResumeForward && !mRecoveringFromDivergence) { - // We processed this request normally. Remember the response and send it to - // the middleman process. - MOZ_RELEASE_ASSERT(index == mRequestIndex); - mRequests[index].mResponseBuffer.append(responseBuffer.begin(), - responseBuffer.length()); - child::RespondToRequest(responseBuffer); - return; - } - - if (mResumeForward) { - // We rewound to erase side effects from the temporary checkpoint we saved - // under ProcessRequest. Just start running forward. - MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence); - gNavigation->mForwardPhase.Enter(mPoint); - return; - } - - // We rewound after having an unhandled recording divergence while processing - // mRequests[index] or some later request. We need to redo all requests up to - // the last request we received. - - // Remember that the last request triggered an unhandled divergence. - MOZ_RELEASE_ASSERT(!mRequests.back().mUnhandledDivergence); - mRequests.back().mUnhandledDivergence = true; - - for (size_t i = index; i < mRequests.length(); i++) { - RequestInfo& info = mRequests[i]; - mRequestIndex = i; - - if (i == index) { - // We just performed this request, and responseBuffer has the right - // contents. - } else { - responseBuffer.clear(); - js::ProcessRequest(info.mRequestBuffer.begin(), - info.mRequestBuffer.length(), &responseBuffer); - } - - if (i < mRequests.length() - 1) { - // This is an old request, and we don't need to send another - // response to it. Make sure the response we just generated matched - // the earlier one we sent, though. - MOZ_RELEASE_ASSERT(responseBuffer.length() == - info.mResponseBuffer.length()); - MOZ_RELEASE_ASSERT( - memcmp(responseBuffer.begin(), info.mResponseBuffer.begin(), - responseBuffer.length() * sizeof(char16_t)) == 0); - } else { - // This is the current request we need to respond to. - MOZ_RELEASE_ASSERT(info.mResponseBuffer.empty()); - info.mResponseBuffer.append(responseBuffer.begin(), - responseBuffer.length()); - child::RespondToRequest(responseBuffer); - } - } - - // We've finished recovering, and can now process new incoming requests. - mRecoveringFromDivergence = false; -} - -bool PausedPhase::MaybeDivergeFromRecording() { - if (!ThisProcessCanRewind()) { - // Recording divergence is not supported if we can't rewind. We can't - // simply allow execution to proceed from here as if we were not - // diverged, since any events or other activity that show up afterwards - // will not be reflected in the recording. - return false; - } - - size_t index = mRequestIndex; - - if (!EnsureTemporaryCheckpoint()) { - // One of the premature exit cases was hit in EnsureTemporaryCheckpoint. - // Don't allow any operations that can diverge from the recording. - return false; - } - - if (mRequests[index].mUnhandledDivergence) { - // We tried to process this request before and had an unhandled divergence. - // Disallow the request handler from doing anything that might diverge from - // the recording. - return false; - } - - DivergeFromRecording(); - return true; -} - -bool PausedPhase::EnsureTemporaryCheckpoint() { - if (mSavedTemporaryCheckpoint) { - return true; - } - - // We need to save a temporary checkpoint that we can restore if we hit - // a recording divergence. - mSavedTemporaryCheckpoint = true; - - size_t index = mRequestIndex; - if (gNavigation->SaveTemporaryCheckpoint(mPoint)) { - // We just saved the temporary checkpoint. - return true; - } - - // We just rewound here. - if (gNavigation->mPhase != this) { - // We are no longer paused at this point. We should be searching - // backwards in the region after this temporary checkpoint was taken. - // Return false to ensure we don't perform any side effects before - // resuming forward. - return false; - } - - // We are still paused at this point. Either we had an unhandled - // recording divergence, or we intentionally rewound to erase side - // effects that occurred while paused here. - MOZ_RELEASE_ASSERT(!mRecoveringFromDivergence); - - if (mResumeForward) { - // We can't diverge from the recording before resuming forward execution. - return false; - } - - mRecoveringFromDivergence = true; - - if (index == mRequestIndex) { - // We had an unhandled divergence for the same request where we - // created the temporary checkpoint. mUnhandledDivergence hasn't been - // set yet, but return now to avoid triggering the same divergence - // and rewinding again. - return false; - } - - // Allow the caller to check mUnhandledDivergence. - return true; -} - -ExecutionPoint PausedPhase::CurrentExecutionPoint() { return mPoint; } - -/////////////////////////////////////////////////////////////////////////////// -// ForwardPhase -/////////////////////////////////////////////////////////////////////////////// - -void ForwardPhase::Enter(const ExecutionPoint& aPoint) { - mPoint = aPoint; - - gNavigation->SetPhase(this); - - // Install handlers for all breakpoints. - for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) { - js::EnsurePositionHandler(breakpoint); - } - - ResumeExecution(); -} - -void ForwardPhase::AfterCheckpoint(const CheckpointId& aCheckpoint) { - MOZ_RELEASE_ASSERT(!aCheckpoint.mTemporary && - aCheckpoint.mNormal == mPoint.mCheckpoint + 1); - gNavigation->mPausedPhase.Enter( - gNavigation->CheckpointExecutionPoint(aCheckpoint.mNormal)); -} - -void ForwardPhase::PositionHit(const ExecutionPoint& aPoint) { - bool hitBreakpoint = false; - for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) { - if (breakpoint.Subsumes(aPoint.mPosition)) { - hitBreakpoint = true; - } - } - - if (hitBreakpoint) { - gNavigation->mPausedPhase.Enter(aPoint); - } -} - -void ForwardPhase::HitRecordingEndpoint(const ExecutionPoint& aPoint) { - gNavigation->mPausedPhase.Enter(aPoint, /* aRewind = */ false, - /* aRecordingEndpoint = */ true); -} - -bool ForwardPhase::ShouldSendPaintMessage() { return true; } - -/////////////////////////////////////////////////////////////////////////////// -// ReachBreakpointPhase -/////////////////////////////////////////////////////////////////////////////// - -void ReachBreakpointPhase::Enter( - const CheckpointId& aStart, bool aRewind, const ExecutionPoint& aPoint, - const Maybe& aTemporaryCheckpoint) { - MOZ_RELEASE_ASSERT(aPoint.HasPosition()); - MOZ_RELEASE_ASSERT(aTemporaryCheckpoint.isNothing() || - (aTemporaryCheckpoint.ref().HasPosition() && - aTemporaryCheckpoint.ref() != aPoint)); - mStart = aStart; - mPoint = aPoint; - mTemporaryCheckpoint = aTemporaryCheckpoint; - mSavedTemporaryCheckpoint = false; - - gNavigation->SetPhase(this); - - if (aRewind) { - RestoreCheckpointAndResume(aStart); - Unreachable(); - } else { - AfterCheckpoint(aStart); - } -} - -void ReachBreakpointPhase::AfterCheckpoint(const CheckpointId& aCheckpoint) { - // We can't run past our target point. - MOZ_RELEASE_ASSERT(aCheckpoint.mNormal <= mPoint.mCheckpoint); - - if (aCheckpoint == mStart) { - // Remember the time we started running forwards from the initial - // checkpoint. - mStartTime = CurrentTime(); - } - - js::EnsurePositionHandler(mPoint.mPosition); - - if (mTemporaryCheckpoint.isSome()) { - js::EnsurePositionHandler(mTemporaryCheckpoint.ref().mPosition); - } -} - -// The number of milliseconds to elapse during a ReachBreakpoint search before -// we will save a temporary checkpoint. -static const double kTemporaryCheckpointThresholdMs = 10; - -void AlwaysSaveTemporaryCheckpoints() { - gNavigation->mAlwaysSaveTemporaryCheckpoints = true; -} - -void ReachBreakpointPhase::PositionHit(const ExecutionPoint& aPoint) { - if (mTemporaryCheckpoint.isSome() && mTemporaryCheckpoint.ref() == aPoint) { - // We've reached the point at which we have the option of saving a - // temporary checkpoint. - double elapsedMs = (CurrentTime() - mStartTime) / 1000.0; - if (elapsedMs >= kTemporaryCheckpointThresholdMs || - gNavigation->mAlwaysSaveTemporaryCheckpoints) { - MOZ_RELEASE_ASSERT(!mSavedTemporaryCheckpoint); - mSavedTemporaryCheckpoint = true; - - if (!gNavigation->SaveTemporaryCheckpoint(aPoint)) { - // We just restored the checkpoint, and could be in any phase. - gNavigation->PositionHit(aPoint); - return; - } - } - } - - if (mPoint == aPoint) { - gNavigation->mPausedPhase.Enter(aPoint); - } -} - -bool ReachBreakpointPhase::ShouldSendPaintMessage() { - // We don't need to send paint messages when reaching the breakpoint, as we - // will be pausing at the breakpoint and doing a repaint of the state there. - return false; -} - -/////////////////////////////////////////////////////////////////////////////// -// FindLastHitPhase -/////////////////////////////////////////////////////////////////////////////// - -void FindLastHitPhase::Enter(const CheckpointId& aStart, - const ExecutionPoint& aEnd, bool aIncludeEnd) { - mStart = aStart; - mEnd = aEnd; - mIncludeEnd = aIncludeEnd; - mCounter = 0; - mTrackedPositions.clear(); - - gNavigation->SetPhase(this); - - // All breakpoints are tracked positions. - for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) { - if (breakpoint.IsValid()) { - mTrackedPositions.emplaceBack(breakpoint); - } - } - - // Entry points to scripts containing breakpoints are tracked positions. - for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) { - Maybe entry = GetEntryPosition(breakpoint); - if (entry.isSome()) { - mTrackedPositions.emplaceBack(entry.ref()); - } - } - - RestoreCheckpointAndResume(mStart); - Unreachable(); -} - -void FindLastHitPhase::AfterCheckpoint(const CheckpointId& aCheckpoint) { - // We can't run past our endpoint. - MOZ_RELEASE_ASSERT(aCheckpoint.mNormal <= mEnd.mCheckpoint); - - if (!mEnd.HasPosition() && mEnd.mCheckpoint == aCheckpoint.mNormal) { - MOZ_RELEASE_ASSERT(!aCheckpoint.mTemporary); - OnRegionEnd(); - Unreachable(); - } - - for (const TrackedPosition& tracked : mTrackedPositions) { - js::EnsurePositionHandler(tracked.mPosition); - } - - if (mEnd.HasPosition()) { - js::EnsurePositionHandler(mEnd.mPosition); - } -} - -void FindLastHitPhase::PositionHit(const ExecutionPoint& aPoint) { - if (!mIncludeEnd) { - CheckForRegionEnd(aPoint); - } - - ++mCounter; - - for (TrackedPosition& tracked : mTrackedPositions) { - if (tracked.mPosition.Subsumes(aPoint.mPosition)) { - tracked.mLastHit = aPoint; - tracked.mLastHitCount = mCounter; - break; - } - } - - if (mIncludeEnd) { - CheckForRegionEnd(aPoint); - } -} - -void FindLastHitPhase::CheckForRegionEnd(const ExecutionPoint& aPoint) { - if (mEnd == aPoint) { - OnRegionEnd(); - Unreachable(); - } -} - -void FindLastHitPhase::HitRecordingEndpoint(const ExecutionPoint& aPoint) { - OnRegionEnd(); - Unreachable(); -} - -bool FindLastHitPhase::ShouldSendPaintMessage() { - // If the region we're searching contains multiple normal checkpoints, we - // only want to send paint messages for the first one. We won't pause at the - // later checkpoints, and sending paint messages for them will clobber the - // one for the checkpoint we will end up pausing at. - return gNavigation->LastCheckpoint().mNormal == mStart.mNormal; -} - -const FindLastHitPhase::TrackedPosition& FindLastHitPhase::FindTrackedPosition( - const BreakpointPosition& aPos) { - for (const TrackedPosition& tracked : mTrackedPositions) { - if (tracked.mPosition == aPos) { - return tracked; - } - } - MOZ_CRASH("Could not find tracked position"); -} - -void FindLastHitPhase::OnRegionEnd() { - // Find the point of the last hit which coincides with a breakpoint. - Maybe lastBreakpoint; - for (const BreakpointPosition& breakpoint : gNavigation->mBreakpoints) { - const TrackedPosition& tracked = FindTrackedPosition(breakpoint); - if (tracked.mLastHit.HasPosition() && - (lastBreakpoint.isNothing() || - lastBreakpoint.ref().mLastHitCount < tracked.mLastHitCount)) { - lastBreakpoint = Some(tracked); - } - } - - if (lastBreakpoint.isNothing()) { - // No breakpoints were encountered in the search space. - if (mStart.mTemporary) { - // We started searching forwards from a temporary checkpoint. - // Continue searching backwards without notifying the middleman. - CheckpointId start = GetLastSavedCheckpointPriorTo(mStart); - start = SkipUnknownTemporaryCheckpoints(start); - ExecutionPoint end = gNavigation->LastTemporaryCheckpointLocation(); - if (end.HasPosition() || end.mCheckpoint != start.mNormal) { - // The temporary checkpoint comes immediately after its associated - // execution point. As we search backwards we need to look for hits at - // that execution point itself. - gNavigation->mFindLastHitPhase.Enter(start, end, - /* aIncludeEnd = */ true); - Unreachable(); - } else { - // The last temporary checkpoint may be at the same execution point as - // the last normal checkpoint, if it was created while handling - // debugger requests there. - } - } - - // Rewind to the last normal checkpoint and pause. - gNavigation->mPausedPhase.Enter( - gNavigation->CheckpointExecutionPoint(mStart.mNormal), - /* aRewind = */ true); - Unreachable(); - } - - // When running backwards, we don't want to place temporary checkpoints at - // the breakpoint where we are going to stop at. If the user continues - // rewinding then we will just have to discard the checkpoint and waste the - // work we did in saving it. - // - // Instead, try to place a temporary checkpoint at the last time the - // breakpoint's script was entered. This optimizes for the case of stepping - // around within a frame. - Maybe baseEntry = - GetEntryPosition(lastBreakpoint.ref().mPosition); - if (baseEntry.isSome()) { - const TrackedPosition& tracked = FindTrackedPosition(baseEntry.ref()); - if (tracked.mLastHit.HasPosition() && - tracked.mLastHitCount < lastBreakpoint.ref().mLastHitCount) { - gNavigation->mReachBreakpointPhase.Enter(mStart, /* aRewind = */ true, - lastBreakpoint.ref().mLastHit, - Some(tracked.mLastHit)); - Unreachable(); - } - } - - // There was no suitable place for a temporary checkpoint, so rewind to the - // last checkpoint and play forward to the last breakpoint hit we found. - gNavigation->mReachBreakpointPhase.Enter( - mStart, /* aRewind = */ true, lastBreakpoint.ref().mLastHit, Nothing()); - Unreachable(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Hooks -/////////////////////////////////////////////////////////////////////////////// - -bool IsInitialized() { return !!gNavigation; } - -void BeforeCheckpoint() { - if (!IsInitialized()) { - void* navigationMem = - AllocateMemory(sizeof(NavigationState), MemoryKind::Navigation); - gNavigation = new (navigationMem) NavigationState(); - - js::SetupDevtoolsSandbox(); - - // Set the progress counter to zero before the first checkpoint. Execution - // that occurred before this checkpoint cannot be rewound to. - *ExecutionProgressCounter() = 0; - } - - AutoDisallowThreadEvents disallow; - - // Reset the debugger to a consistent state before each checkpoint. - js::ClearPositionHandlers(); -} - -void AfterCheckpoint(const CheckpointId& aCheckpoint) { - AutoDisallowThreadEvents disallow; - - MOZ_RELEASE_ASSERT(IsRecordingOrReplaying()); - gNavigation->AfterCheckpoint(aCheckpoint); -} - -size_t LastNormalCheckpoint() { return gNavigation->LastCheckpoint().mNormal; } - -void DebuggerRequest(js::CharBuffer* aRequestBuffer) { - gNavigation->HandleDebuggerRequest(aRequestBuffer); -} - -void AddBreakpoint(const BreakpointPosition& aPosition) { - gNavigation->mBreakpoints.append(aPosition); -} - -void ClearBreakpoints() { - if (gNavigation) { - gNavigation->mBreakpoints.clear(); - } -} - -void Resume(bool aForward) { - // For the primordial resume sent at startup, the navigation state will not - // have been initialized yet. - if (!gNavigation) { - ResumeExecution(); - return; - } - gNavigation->Resume(aForward); -} - -void RestoreCheckpoint(size_t aId) { gNavigation->RestoreCheckpoint(aId); } - -void RunToPoint(const ExecutionPoint& aTarget) { - gNavigation->RunToPoint(aTarget); -} - -ExecutionPoint GetRecordingEndpoint() { - if (IsRecording()) { - return gNavigation->CurrentExecutionPoint(); - } else { - return gNavigation->LastRecordingEndpoint(); - } -} - -void SetRecordingEndpoint(size_t aIndex, const ExecutionPoint& aEndpoint) { - MOZ_RELEASE_ASSERT(IsReplaying()); - gNavigation->SetRecordingEndpoint(aIndex, aEndpoint); -} - -static ProgressCounter gProgressCounter; - -extern "C" { - -MOZ_EXPORT ProgressCounter* RecordReplayInterface_ExecutionProgressCounter() { - return &gProgressCounter; -} - -} // extern "C" - -ExecutionPoint CurrentExecutionPoint( - const Maybe& aPosition) { - if (aPosition.isSome()) { - return ExecutionPoint(gNavigation->LastCheckpoint().mNormal, - gProgressCounter, aPosition.ref()); - } - return gNavigation->CurrentExecutionPoint(); -} - -void PositionHit(const BreakpointPosition& position) { - AutoDisallowThreadEvents disallow; - gNavigation->PositionHit(CurrentExecutionPoint(Some(position))); -} - -extern "C" { - -MOZ_EXPORT ProgressCounter RecordReplayInterface_NewTimeWarpTarget() { - if (AreThreadEventsDisallowed()) { - return 0; - } - - // NewTimeWarpTarget() must be called at consistent points between recording - // and replaying. - RecordReplayAssert("NewTimeWarpTarget"); - - if (!gNavigation) { - return 0; - } - - // Advance the progress counter for each time warp target. This can be called - // at any place and any number of times where recorded events are allowed. - ProgressCounter progress = ++gProgressCounter; - - PositionHit(BreakpointPosition(BreakpointPosition::WarpTarget)); - return progress; -} - -} // extern "C" - -ExecutionPoint TimeWarpTargetExecutionPoint(ProgressCounter aTarget) { - // To construct an ExecutionPoint, we need the most recent checkpoint prior - // to aTarget. We could do a binary search here, but this code is cold and a - // linear search is more straightforwardly correct. - size_t checkpoint; - for (checkpoint = gNavigation->mCheckpointProgress.length() - 1; - checkpoint >= CheckpointId::First; checkpoint--) { - if (gNavigation->mCheckpointProgress[checkpoint] < aTarget) { - break; - } - } - MOZ_RELEASE_ASSERT(checkpoint >= CheckpointId::First); - - return ExecutionPoint(checkpoint, aTarget, - BreakpointPosition(BreakpointPosition::WarpTarget)); -} - -bool MaybeDivergeFromRecording() { - return gNavigation->MaybeDivergeFromRecording(); -} - -bool ShouldSendPaintMessage() { return gNavigation->ShouldSendPaintMessage(); } - -} // namespace navigation -} // namespace recordreplay -} // namespace mozilla diff --git a/toolkit/recordreplay/ipc/ChildProcess.cpp b/toolkit/recordreplay/ipc/ChildProcess.cpp index 532a82650ec0..812f5e3cb74a 100644 --- a/toolkit/recordreplay/ipc/ChildProcess.cpp +++ b/toolkit/recordreplay/ipc/ChildProcess.cpp @@ -53,8 +53,7 @@ ChildProcessInfo::~ChildProcessInfo() { } } -void ChildProcessInfo::OnIncomingMessage(const Message& aMsg, - bool aForwardToControl) { +void ChildProcessInfo::OnIncomingMessage(const Message& aMsg) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); mLastMessageTime = TimeStamp::Now(); @@ -69,27 +68,12 @@ void ChildProcessInfo::OnIncomingMessage(const Message& aMsg, OnCrash(nmsg.Error()); return; } - case MessageType::HitExecutionPoint: { - const HitExecutionPointMessage& nmsg = - static_cast(aMsg); - mPaused = true; - if (this == GetActiveChild() && !nmsg.mPoint.HasPosition()) { - MaybeUpdateGraphicsAtCheckpoint(nmsg.mPoint.mCheckpoint); - } - if (aForwardToControl) { - js::ForwardHitExecutionPointMessage(GetId(), nmsg); - } - break; - } case MessageType::Paint: - MaybeUpdateGraphicsAtPaint(static_cast(aMsg)); + UpdateGraphicsInUIProcess(&static_cast(aMsg)); break; - case MessageType::DebuggerResponse: - mPaused = true; - js::OnDebuggerResponse(aMsg); - break; - case MessageType::RecordingFlushed: + case MessageType::ManifestFinished: mPaused = true; + js::ForwardManifestFinished(this, aMsg); break; case MessageType::MiddlemanCallRequest: { const MiddlemanCallRequestMessage& nmsg = @@ -115,16 +99,8 @@ void ChildProcessInfo::SendMessage(Message&& aMsg) { // Update paused state. MOZ_RELEASE_ASSERT(IsPaused() || aMsg.CanBeSentWhileUnpaused()); - switch (aMsg.mType) { - case MessageType::Resume: - case MessageType::RestoreCheckpoint: - case MessageType::RunToPoint: - case MessageType::DebuggerRequest: - case MessageType::FlushRecording: - mPaused = false; - break; - default: - break; + if (aMsg.mType == MessageType::ManifestStart) { + mPaused = false; } mLastMessageTime = TimeStamp::Now(); @@ -203,20 +179,8 @@ void ChildProcessInfo::LaunchSubprocess( SendGraphicsMemoryToChild(); - // The child should send us a HitExecutionPoint message with an invalid point - // to pause. - WaitUntilPaused(); - MOZ_RELEASE_ASSERT(gIntroductionMessage); SendMessage(std::move(*gIntroductionMessage)); - - // Always save the first checkpoint in replaying child processes. - if (!IsRecording()) { - SendMessage(SetSaveCheckpointMessage(CheckpointId::First, true)); - } - - // Always run forward to the first checkpoint after the primordial one. - SendMessage(ResumeMessage(/* aForward = */ true)); } void ChildProcessInfo::OnCrash(const char* aWhy) { @@ -291,11 +255,11 @@ static bool gHasPendingMessageRunnable; // considering that child to be hung. static const size_t HangSeconds = 30; -Message::UniquePtr ChildProcessInfo::WaitUntilPaused() { +void ChildProcessInfo::WaitUntilPaused() { MOZ_RELEASE_ASSERT(NS_IsMainThread()); if (IsPaused()) { - return nullptr; + return; } bool sentTerminateMessage = false; @@ -307,9 +271,9 @@ Message::UniquePtr ChildProcessInfo::WaitUntilPaused() { Message::UniquePtr msg = ExtractChildMessage(&process); if (msg) { - OnIncomingMessage(*msg, /* aForwardToControl = */ false); + OnIncomingMessage(*msg); if (IsPaused()) { - return msg; + return; } } else { if (gChildrenAreDebugging || IsRecording()) { @@ -357,7 +321,7 @@ void ChildProcessInfo::MaybeProcessPendingMessageRunnable() { if (msg) { MonitorAutoUnlock unlock(*gMonitor); - process->OnIncomingMessage(*msg, /* aForwardToControl = */ true); + process->OnIncomingMessage(*msg); } else { break; } diff --git a/toolkit/recordreplay/ipc/JSControl.cpp b/toolkit/recordreplay/ipc/JSControl.cpp index f12f4ea3a459..7f47bc1f47c6 100644 --- a/toolkit/recordreplay/ipc/JSControl.cpp +++ b/toolkit/recordreplay/ipc/JSControl.cpp @@ -42,34 +42,6 @@ static JSObject* NonNullObject(JSContext* aCx, HandleValue aValue) { return &aValue.toObject(); } -template -static bool MaybeGetNumberProperty(JSContext* aCx, HandleObject aObject, - const char* aProperty, T* aResult) { - RootedValue v(aCx); - if (!JS_GetProperty(aCx, aObject, aProperty, &v)) { - return false; - } - if (v.isNumber()) { - *aResult = v.toNumber(); - } - return true; -} - -template -static bool GetNumberProperty(JSContext* aCx, HandleObject aObject, - const char* aProperty, T* aResult) { - RootedValue v(aCx); - if (!JS_GetProperty(aCx, aObject, aProperty, &v)) { - return false; - } - if (!v.isNumber()) { - JS_ReportErrorASCII(aCx, "Object missing required property"); - return false; - } - *aResult = v.toNumber(); - return true; -} - static parent::ChildProcessInfo* GetChildById(JSContext* aCx, const Value& aValue, bool aAllowUnpaused = false) { @@ -85,157 +57,6 @@ static parent::ChildProcessInfo* GetChildById(JSContext* aCx, return child; } -/////////////////////////////////////////////////////////////////////////////// -// BreakpointPosition Conversion -/////////////////////////////////////////////////////////////////////////////// - -// Names of properties which JS code uses to specify the contents of a -// BreakpointPosition. -static const char gKindProperty[] = "kind"; -static const char gScriptProperty[] = "script"; -static const char gOffsetProperty[] = "offset"; -static const char gFrameIndexProperty[] = "frameIndex"; - -JSObject* BreakpointPosition::Encode(JSContext* aCx) const { - RootedString kindString(aCx, JS_NewStringCopyZ(aCx, KindString())); - RootedObject obj(aCx, JS_NewObject(aCx, nullptr)); - if (!kindString || !obj || - !JS_DefineProperty(aCx, obj, gKindProperty, kindString, - JSPROP_ENUMERATE) || - (mScript != BreakpointPosition::EMPTY_SCRIPT && - !JS_DefineProperty(aCx, obj, gScriptProperty, mScript, - JSPROP_ENUMERATE)) || - (mOffset != BreakpointPosition::EMPTY_OFFSET && - !JS_DefineProperty(aCx, obj, gOffsetProperty, mOffset, - JSPROP_ENUMERATE)) || - (mFrameIndex != BreakpointPosition::EMPTY_FRAME_INDEX && - !JS_DefineProperty(aCx, obj, gFrameIndexProperty, mFrameIndex, - JSPROP_ENUMERATE))) { - return nullptr; - } - return obj; -} - -bool BreakpointPosition::Decode(JSContext* aCx, HandleObject aObject) { - RootedValue v(aCx); - if (!JS_GetProperty(aCx, aObject, gKindProperty, &v)) { - return false; - } - - RootedString str(aCx, ::ToString(aCx, v)); - for (size_t i = BreakpointPosition::Invalid + 1; - i < BreakpointPosition::sKindCount; i++) { - BreakpointPosition::Kind kind = (BreakpointPosition::Kind)i; - bool match; - if (!JS_StringEqualsAscii( - aCx, str, BreakpointPosition::StaticKindString(kind), &match)) - return false; - if (match) { - mKind = kind; - break; - } - } - if (mKind == BreakpointPosition::Invalid) { - JS_ReportErrorASCII(aCx, "Could not decode breakpoint position kind"); - return false; - } - - if (!MaybeGetNumberProperty(aCx, aObject, gScriptProperty, &mScript) || - !MaybeGetNumberProperty(aCx, aObject, gOffsetProperty, &mOffset) || - !MaybeGetNumberProperty(aCx, aObject, gFrameIndexProperty, - &mFrameIndex)) { - return false; - } - - return true; -} - -void BreakpointPosition::ToString(nsCString& aStr) const { - aStr.AppendPrintf("{ Kind: %s, Script: %d, Offset: %d, Frame: %d }", - KindString(), (int)mScript, (int)mOffset, (int)mFrameIndex); -} - -/////////////////////////////////////////////////////////////////////////////// -// ExecutionPoint Conversion -/////////////////////////////////////////////////////////////////////////////// - -// Names of properties which JS code uses to specify the contents of an -// ExecutionPoint. -static const char gCheckpointProperty[] = "checkpoint"; -static const char gProgressProperty[] = "progress"; -static const char gPositionProperty[] = "position"; - -JSObject* ExecutionPoint::Encode(JSContext* aCx) const { - RootedObject obj(aCx, JS_NewObject(aCx, nullptr)); - if (!obj || - !JS_DefineProperty(aCx, obj, gCheckpointProperty, (double)mCheckpoint, - JSPROP_ENUMERATE) || - !JS_DefineProperty(aCx, obj, gProgressProperty, (double)mProgress, - JSPROP_ENUMERATE)) { - return nullptr; - } - if (HasPosition()) { - RootedObject position(aCx, mPosition.Encode(aCx)); - if (!position || !JS_DefineProperty(aCx, obj, gPositionProperty, position, - JSPROP_ENUMERATE)) { - return nullptr; - } - } - return obj; -} - -bool ExecutionPoint::Decode(JSContext* aCx, HandleObject aObject) { - RootedValue v(aCx); - if (!JS_GetProperty(aCx, aObject, gPositionProperty, &v)) { - return false; - } - - if (v.isUndefined()) { - MOZ_RELEASE_ASSERT(!HasPosition()); - } else { - RootedObject positionObject(aCx, NonNullObject(aCx, v)); - if (!positionObject || !mPosition.Decode(aCx, positionObject)) { - return false; - } - } - return GetNumberProperty(aCx, aObject, gCheckpointProperty, &mCheckpoint) && - GetNumberProperty(aCx, aObject, gProgressProperty, &mProgress); -} - -void ExecutionPoint::ToString(nsCString& aStr) const { - aStr.AppendPrintf("{ Checkpoint %d", (int)mCheckpoint); - if (HasPosition()) { - aStr.AppendPrintf(" Progress %llu Position ", mProgress); - mPosition.ToString(aStr); - } - aStr.AppendPrintf(" }"); -} - -/////////////////////////////////////////////////////////////////////////////// -// Message Conversion -/////////////////////////////////////////////////////////////////////////////// - -static JSObject* EncodeChannelMessage(JSContext* aCx, - const HitExecutionPointMessage& aMsg) { - RootedObject obj(aCx, JS_NewObject(aCx, nullptr)); - if (!obj) { - return nullptr; - } - - RootedObject pointObject(aCx, aMsg.mPoint.Encode(aCx)); - if (!pointObject || - !JS_DefineProperty(aCx, obj, "point", pointObject, JSPROP_ENUMERATE) || - !JS_DefineProperty(aCx, obj, "recordingEndpoint", aMsg.mRecordingEndpoint, - JSPROP_ENUMERATE) || - !JS_DefineProperty(aCx, obj, "duration", - aMsg.mDurationMicroseconds / 1000.0, - JSPROP_ENUMERATE)) { - return nullptr; - } - - return obj; -} - /////////////////////////////////////////////////////////////////////////////// // Middleman Control /////////////////////////////////////////////////////////////////////////////// @@ -264,19 +85,22 @@ void SetupMiddlemanControl(const Maybe& aRecordingChildId) { } } -void ForwardHitExecutionPointMessage(size_t aId, - const HitExecutionPointMessage& aMsg) { +void ForwardManifestFinished(parent::ChildProcessInfo* aChild, + const Message& aMsg) { MOZ_RELEASE_ASSERT(gControl); + const auto& nmsg = static_cast(aMsg); AutoSafeJSContext cx; JSAutoRealm ar(cx, xpc::PrivilegedJunkScope()); - JSObject* obj = EncodeChannelMessage(cx, aMsg); - MOZ_RELEASE_ASSERT(obj); + RootedValue value(cx); + if (nmsg.BufferSize() && + !JS_ParseJSON(cx, nmsg.Buffer(), nmsg.BufferSize(), &value)) { + MOZ_CRASH("ForwardManifestFinished"); + } - RootedValue value(cx, ObjectValue(*obj)); - if (NS_FAILED(gControl->HitExecutionPoint(aId, value))) { - MOZ_CRASH("ForwardMessageToMiddlemanControl"); + if (NS_FAILED(gControl->ManifestFinished(aChild->GetId(), value))) { + MOZ_CRASH("ForwardManifestFinished"); } } @@ -367,208 +191,30 @@ static bool Middleman_SpawnReplayingChild(JSContext* aCx, unsigned aArgc, return true; } -static bool Middleman_SetActiveChild(JSContext* aCx, unsigned aArgc, - Value* aVp) { +static bool Middleman_SendManifest(JSContext* aCx, unsigned aArgc, Value* aVp) { CallArgs args = CallArgsFromVp(aArgc, aVp); + RootedObject manifestObject(aCx, NonNullObject(aCx, args.get(1))); + if (!manifestObject) { + return false; + } + + CharBuffer manifestBuffer; + if (!ToJSONMaybeSafely(aCx, manifestObject, FillCharBufferCallback, + &manifestBuffer)) { + return false; + } + parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0)); if (!child) { return false; } - parent::SetActiveChild(child); - - args.rval().setUndefined(); - return true; -} - -static bool Middleman_SendSetSaveCheckpoint(JSContext* aCx, unsigned aArgc, - Value* aVp) { - CallArgs args = CallArgsFromVp(aArgc, aVp); - - parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0)); - if (!child) { - return false; - } - - double checkpoint; - if (!ToNumber(aCx, args.get(1), &checkpoint)) { - return false; - } - - bool shouldSave = ToBoolean(args.get(2)); - - child->SendMessage(SetSaveCheckpointMessage(checkpoint, shouldSave)); - - args.rval().setUndefined(); - return true; -} - -static bool Middleman_SendFlushRecording(JSContext* aCx, unsigned aArgc, - Value* aVp) { - CallArgs args = CallArgsFromVp(aArgc, aVp); - - parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0)); - if (!child) { - return false; - } - - child->SendMessage(FlushRecordingMessage()); - - // The child will unpause until the flush finishes. - child->WaitUntilPaused(); - - args.rval().setUndefined(); - return true; -} - -static bool Middleman_SendResume(JSContext* aCx, unsigned aArgc, Value* aVp) { - CallArgs args = CallArgsFromVp(aArgc, aVp); - - parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0)); - if (!child) { - return false; - } - - bool forward = ToBoolean(args.get(1)); - - child->SendMessage(ResumeMessage(forward)); - - args.rval().setUndefined(); - return true; -} - -static bool Middleman_SendRestoreCheckpoint(JSContext* aCx, unsigned aArgc, - Value* aVp) { - CallArgs args = CallArgsFromVp(aArgc, aVp); - - parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0)); - if (!child) { - return false; - } - - double checkpoint; - if (!ToNumber(aCx, args.get(1), &checkpoint)) { - return false; - } - - child->SendMessage(RestoreCheckpointMessage(checkpoint)); - - args.rval().setUndefined(); - return true; -} - -static bool Middleman_SendRunToPoint(JSContext* aCx, unsigned aArgc, - Value* aVp) { - CallArgs args = CallArgsFromVp(aArgc, aVp); - - parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0)); - if (!child) { - return false; - } - - RootedObject pointObject(aCx, NonNullObject(aCx, args.get(1))); - if (!pointObject) { - return false; - } - - ExecutionPoint point; - if (!point.Decode(aCx, pointObject)) { - return false; - } - - child->SendMessage(RunToPointMessage(point)); - - args.rval().setUndefined(); - return true; -} - -// Buffer for receiving the next debugger response. -static js::CharBuffer* gResponseBuffer; - -void OnDebuggerResponse(const Message& aMsg) { - const DebuggerResponseMessage& nmsg = - static_cast(aMsg); - MOZ_RELEASE_ASSERT(gResponseBuffer && gResponseBuffer->empty()); - gResponseBuffer->append(nmsg.Buffer(), nmsg.BufferSize()); -} - -static bool Middleman_SendDebuggerRequest(JSContext* aCx, unsigned aArgc, - Value* aVp) { - CallArgs args = CallArgsFromVp(aArgc, aVp); - - parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0)); - if (!child) { - return false; - } - - RootedObject requestObject(aCx, NonNullObject(aCx, args.get(1))); - if (!requestObject) { - return false; - } - - CharBuffer requestBuffer; - if (!ToJSONMaybeSafely(aCx, requestObject, FillCharBufferCallback, - &requestBuffer)) { - return false; - } - - CharBuffer responseBuffer; - - MOZ_RELEASE_ASSERT(!gResponseBuffer); - gResponseBuffer = &responseBuffer; - - DebuggerRequestMessage* msg = DebuggerRequestMessage::New( - requestBuffer.begin(), requestBuffer.length()); + ManifestStartMessage* msg = ManifestStartMessage::New( + manifestBuffer.begin(), manifestBuffer.length()); child->SendMessage(std::move(*msg)); free(msg); - // Wait for the child to respond to the query. - child->WaitUntilPaused(); - MOZ_RELEASE_ASSERT(gResponseBuffer == &responseBuffer); - MOZ_RELEASE_ASSERT(gResponseBuffer->length() != 0); - gResponseBuffer = nullptr; - - return JS_ParseJSON(aCx, responseBuffer.begin(), responseBuffer.length(), - args.rval()); -} - -static bool Middleman_SendAddBreakpoint(JSContext* aCx, unsigned aArgc, - Value* aVp) { - CallArgs args = CallArgsFromVp(aArgc, aVp); - - parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0)); - if (!child) { - return false; - } - - RootedObject positionObject(aCx, NonNullObject(aCx, args.get(1))); - if (!positionObject) { - return false; - } - - BreakpointPosition position; - if (!position.Decode(aCx, positionObject)) { - return false; - } - - child->SendMessage(AddBreakpointMessage(position)); - - args.rval().setUndefined(); - return true; -} - -static bool Middleman_SendClearBreakpoints(JSContext* aCx, unsigned aArgc, - Value* aVp) { - CallArgs args = CallArgsFromVp(aArgc, aVp); - - parent::ChildProcessInfo* child = GetChildById(aCx, args.get(0)); - if (!child) { - return false; - } - - child->SendMessage(ClearBreakpointsMessage()); - args.rval().setUndefined(); return true; } @@ -584,7 +230,7 @@ static bool Middleman_HadRepaint(JSContext* aCx, unsigned aArgc, Value* aVp) { size_t width = args.get(0).toNumber(); size_t height = args.get(1).toNumber(); - PaintMessage message(CheckpointId::Invalid, width, height); + PaintMessage message(InvalidCheckpointId, width, height); parent::UpdateGraphicsInUIProcess(&message); args.rval().setUndefined(); @@ -632,50 +278,9 @@ static bool Middleman_WaitUntilPaused(JSContext* aCx, unsigned aArgc, MaybeCreateCheckpointInChild(child); } - Message::UniquePtr msg = child->WaitUntilPaused(); + child->WaitUntilPaused(); - if (!msg) { - JS_ReportErrorASCII(aCx, "Child process is already paused"); - } - - MOZ_RELEASE_ASSERT(msg->mType == MessageType::HitExecutionPoint); - const HitExecutionPointMessage& nmsg = - static_cast(*msg); - - JSObject* obj = EncodeChannelMessage(aCx, nmsg); - if (!obj) { - return false; - } - - args.rval().setObject(*obj); - return true; -} - -static bool Middleman_PositionSubsumes(JSContext* aCx, unsigned aArgc, - Value* aVp) { - CallArgs args = CallArgsFromVp(aArgc, aVp); - - RootedObject firstPositionObject(aCx, NonNullObject(aCx, args.get(0))); - if (!firstPositionObject) { - return false; - } - - BreakpointPosition firstPosition; - if (!firstPosition.Decode(aCx, firstPositionObject)) { - return false; - } - - RootedObject secondPositionObject(aCx, NonNullObject(aCx, args.get(1))); - if (!secondPositionObject) { - return false; - } - - BreakpointPosition secondPosition; - if (!secondPosition.Decode(aCx, secondPositionObject)) { - return false; - } - - args.rval().setBoolean(firstPosition.Subsumes(secondPosition)); + args.rval().setUndefined(); return true; } @@ -691,6 +296,8 @@ static StaticRefPtr gReplay; // Whether to expose chrome:// and resource:// scripts to the debugger. static bool gIncludeSystemScripts; +static void InitializeScriptHits(); + void SetupDevtoolsSandbox() { MOZ_RELEASE_ASSERT(!gReplay); @@ -702,6 +309,12 @@ void SetupDevtoolsSandbox() { gIncludeSystemScripts = Preferences::GetBool("devtools.recordreplay.includeSystemScripts"); + + InitializeScriptHits(); +} + +bool IsInitialized() { + return !!gReplay; } extern "C" { @@ -725,103 +338,85 @@ MOZ_EXPORT bool RecordReplayInterface_ShouldUpdateProgressCounter( #undef ReplayScriptURL -void ProcessRequest(const char16_t* aRequest, size_t aRequestLength, - CharBuffer* aResponse) { +void ManifestStart(const CharBuffer& aContents) { AutoDisallowThreadEvents disallow; AutoSafeJSContext cx; JSAutoRealm ar(cx, xpc::PrivilegedJunkScope()); - RootedValue requestValue(cx); - if (!JS_ParseJSON(cx, aRequest, aRequestLength, &requestValue)) { - MOZ_CRASH("ProcessRequest: ParseJSON failed"); + RootedValue value(cx); + if (!JS_ParseJSON(cx, aContents.begin(), aContents.length(), &value)) { + MOZ_CRASH("ManifestStart: ParseJSON failed"); } - RootedValue responseValue(cx); - if (NS_FAILED(gReplay->ProcessRequest(requestValue, &responseValue))) { - MOZ_CRASH("ProcessRequest: Handler failed"); + if (NS_FAILED(gReplay->ManifestStart(value))) { + MOZ_CRASH("ManifestStart: Handler failed"); } - // Processing the request may have called into MaybeDivergeFromRecording. - // Now that we've finished processing it, don't tolerate future events that + // Processing the manifest may have called into MaybeDivergeFromRecording. + // If it did so, we should already have finished any processing that required + // diverging from the recording. Don't tolerate future events that // would otherwise cause us to rewind to the last checkpoint. DisallowUnhandledDivergeFromRecording(); - - if (!responseValue.isObject()) { - MOZ_CRASH("ProcessRequest: Response must be an object"); - } - - RootedObject responseObject(cx, &responseValue.toObject()); - if (!ToJSONMaybeSafely(cx, responseObject, FillCharBufferCallback, - aResponse)) { - MOZ_CRASH("ProcessRequest: ToJSONMaybeSafely failed"); - } } -void EnsurePositionHandler(const BreakpointPosition& aPosition) { +void BeforeCheckpoint() { + if (!IsInitialized()) { + SetupDevtoolsSandbox(); + } + AutoDisallowThreadEvents disallow; AutoSafeJSContext cx; JSAutoRealm ar(cx, xpc::PrivilegedJunkScope()); - RootedObject obj(cx, aPosition.Encode(cx)); - if (!obj) { - MOZ_CRASH("EnsurePositionHandler"); - } - - RootedValue objValue(cx, ObjectValue(*obj)); - if (NS_FAILED(gReplay->EnsurePositionHandler(objValue))) { - MOZ_CRASH("EnsurePositionHandler"); + if (NS_FAILED(gReplay->BeforeCheckpoint())) { + MOZ_CRASH("BeforeCheckpoint"); } } -void ClearPositionHandlers() { +void AfterCheckpoint(size_t aCheckpoint, bool aRestoredCheckpoint) { AutoDisallowThreadEvents disallow; AutoSafeJSContext cx; JSAutoRealm ar(cx, xpc::PrivilegedJunkScope()); - if (NS_FAILED(gReplay->ClearPositionHandlers())) { - MOZ_CRASH("ClearPositionHandlers"); + if (NS_FAILED(gReplay->AfterCheckpoint(aCheckpoint, aRestoredCheckpoint))) { + MOZ_CRASH("AfterCheckpoint"); } } -void ClearPausedState() { +static ProgressCounter gProgressCounter; + +extern "C" { + +MOZ_EXPORT ProgressCounter* RecordReplayInterface_ExecutionProgressCounter() { + return &gProgressCounter; +} + +MOZ_EXPORT ProgressCounter RecordReplayInterface_NewTimeWarpTarget() { + if (AreThreadEventsDisallowed()) { + return 0; + } + + // NewTimeWarpTarget() must be called at consistent points between recording + // and replaying. + RecordReplayAssert("NewTimeWarpTarget"); + + if (!IsInitialized()) { + return 0; + } + AutoDisallowThreadEvents disallow; AutoSafeJSContext cx; JSAutoRealm ar(cx, xpc::PrivilegedJunkScope()); - if (NS_FAILED(gReplay->ClearPausedState())) { - MOZ_CRASH("ClearPausedState"); + int32_t counter = 0; + if (NS_FAILED(gReplay->NewTimeWarpTarget(&counter))) { + MOZ_CRASH("NewTimeWarpTarget"); } + + return counter; } -Maybe GetEntryPosition( - const BreakpointPosition& aPosition) { - AutoDisallowThreadEvents disallow; - AutoSafeJSContext cx; - JSAutoRealm ar(cx, xpc::PrivilegedJunkScope()); - - RootedObject positionObject(cx, aPosition.Encode(cx)); - if (!positionObject) { - MOZ_CRASH("GetEntryPosition"); - } - - RootedValue rval(cx); - RootedValue positionValue(cx, ObjectValue(*positionObject)); - if (NS_FAILED(gReplay->GetEntryPosition(positionValue, &rval))) { - MOZ_CRASH("GetEntryPosition"); - } - - if (!rval.isObject()) { - return Nothing(); - } - - RootedObject rvalObject(cx, &rval.toObject()); - BreakpointPosition entryPosition; - if (!entryPosition.Decode(cx, rvalObject)) { - MOZ_CRASH("GetEntryPosition"); - } - - return Some(entryPosition); -} +} // extern "C" /////////////////////////////////////////////////////////////////////////////// // Replaying process content @@ -979,10 +574,18 @@ static bool RecordReplay_AreThreadEventsDisallowed(JSContext* aCx, return true; } -static bool RecordReplay_MaybeDivergeFromRecording(JSContext* aCx, - unsigned aArgc, Value* aVp) { +static bool RecordReplay_DivergeFromRecording(JSContext* aCx, + unsigned aArgc, Value* aVp) { CallArgs args = CallArgsFromVp(aArgc, aVp); - args.rval().setBoolean(navigation::MaybeDivergeFromRecording()); + DivergeFromRecording(); + args.rval().setUndefined(); + return true; +} + +static bool RecordReplay_ProgressCounter(JSContext* aCx, unsigned aArgc, + Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + args.rval().setNumber((double) gProgressCounter); return true; } @@ -1024,25 +627,311 @@ static bool RecordReplay_ShouldUpdateProgressCounter(JSContext* aCx, return true; } -static bool RecordReplay_PositionHit(JSContext* aCx, unsigned aArgc, - Value* aVp) { +static bool RecordReplay_ManifestFinished(JSContext* aCx, unsigned aArgc, + Value* aVp) { CallArgs args = CallArgsFromVp(aArgc, aVp); - RootedObject obj(aCx, NonNullObject(aCx, args.get(0))); - if (!obj) { - return false; + + CharBuffer responseBuffer; + if (args.hasDefined(0)) { + RootedObject responseObject(aCx, NonNullObject(aCx, args.get(0))); + if (!responseObject) { + return false; + } + + if (!ToJSONMaybeSafely(aCx, responseObject, FillCharBufferCallback, + &responseBuffer)) { + return false; + } } - BreakpointPosition position; - if (!position.Decode(aCx, obj)) { - return false; - } - - navigation::PositionHit(position); + child::ManifestFinished(responseBuffer); args.rval().setUndefined(); return true; } +static bool RecordReplay_ResumeExecution(JSContext* aCx, unsigned aArgc, + Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + + ResumeExecution(); + + args.rval().setUndefined(); + return true; +} + +static bool RecordReplay_RestoreCheckpoint(JSContext* aCx, unsigned aArgc, + Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + + if (!args.get(0).isNumber()) { + JS_ReportErrorASCII(aCx, "Bad checkpoint ID"); + return false; + } + + size_t checkpoint = args.get(0).toNumber(); + if (!HasSavedCheckpoint(checkpoint)) { + JS_ReportErrorASCII(aCx, "Only saved checkpoints can be restored"); + return false; + } + + RestoreCheckpointAndResume(checkpoint); + + JS_ReportErrorASCII(aCx, "Unreachable!"); + return false; +} + +// The total amount of time this process has spent idling. +static double gIdleTimeTotal; + +// When recording and we are idle, the time when we became idle. +static double gIdleTimeStart; + +void BeginIdleTime() { + if (IsRecording() && Thread::CurrentIsMainThread()) { + MOZ_RELEASE_ASSERT(!gIdleTimeStart); + gIdleTimeStart = CurrentTime(); + } +} + +void EndIdleTime() { + if (IsRecording() && Thread::CurrentIsMainThread()) { + MOZ_RELEASE_ASSERT(!!gIdleTimeStart); + gIdleTimeTotal += CurrentTime() - gIdleTimeStart; + gIdleTimeStart = 0; + } +} + +static bool RecordReplay_CurrentExecutionTime(JSContext* aCx, unsigned aArgc, + Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + + // Get a current timestamp biased by the amount of time the process has spent + // idling. Comparing these timestamps gives the elapsed non-idle time between + // them. + args.rval().setNumber((CurrentTime() - gIdleTimeTotal) / 1000.0); + return true; +} + +static bool RecordReplay_FlushRecording(JSContext* aCx, unsigned aArgc, + Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + + FlushRecording(); + + args.rval().setUndefined(); + return true; +} + +static bool RecordReplay_SetMainChild(JSContext* aCx, unsigned aArgc, + Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + + SetMainChild(); + size_t endpoint = RecordingEndpoint(); + + args.rval().setInt32(endpoint); + return true; +} + +static bool RecordReplay_SaveCheckpoint(JSContext* aCx, unsigned aArgc, + Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + + if (!args.get(0).isNumber()) { + JS_ReportErrorASCII(aCx, "Bad checkpoint ID"); + return false; + } + + size_t checkpoint = args.get(0).toNumber(); + if (checkpoint <= GetLastCheckpoint()) { + JS_ReportErrorASCII(aCx, "Can't save checkpoint in the past"); + return false; + } + + SetSaveCheckpoint(checkpoint, true); + + args.rval().setUndefined(); + return true; +} + +// How many hits on a script location we will precisely track for a checkpoint. +static const size_t MaxHitsPerCheckpoint = 10; + +struct ScriptHitInfo { + // Information about a location where a script offset has been hit, or an + // aggregate set of hits. + struct ScriptHit { + // The most recent checkpoint prior to the hit. + uint32_t mCheckpoint; + + // Index of the frame where the hit occurred, or UINT32_MAX if this represents + // an aggregate set of hits after the checkpoint. + uint32_t mFrameIndex; + + // Progress counter when the hit occurred, invalid if this represents an + // aggregate set of hits. + ProgressCounter mProgress; + + explicit ScriptHit(uint32_t aCheckpoint) + : mCheckpoint(aCheckpoint), mFrameIndex(UINT32_MAX), mProgress(0) {} + + ScriptHit(uint32_t aCheckpoint, uint32_t aFrameIndex, + ProgressCounter aProgress) + : mCheckpoint(aCheckpoint), mFrameIndex(aFrameIndex), + mProgress(aProgress) {} + }; + + struct ScriptHitChunk { + ScriptHit mHits[7]; + ScriptHitChunk* mPrevious; + }; + + struct ScriptHitKey { + uint32_t mScript; + uint32_t mOffset; + + ScriptHitKey(uint32_t aScript, uint32_t aOffset) + : mScript(aScript), mOffset(aOffset) {} + + typedef ScriptHitKey Lookup; + + static HashNumber hash(const ScriptHitKey& aKey) { + return HashGeneric(aKey.mScript, aKey.mOffset); + } + + static bool match(const ScriptHitKey& aFirst, const ScriptHitKey& aSecond) { + return aFirst.mScript == aSecond.mScript + && aFirst.mOffset == aSecond.mOffset; + } + }; + + typedef HashMap> ScriptHitMap; + ScriptHitMap mTable; + ScriptHitChunk* mFreeChunk; + + ScriptHitInfo() : mFreeChunk(nullptr) {} + + ScriptHitChunk* FindHits(uint32_t aScript, uint32_t aOffset) { + ScriptHitKey key(aScript, aOffset); + ScriptHitMap::Ptr p = mTable.lookup(key); + return p ? p->value() : nullptr; + } + + void AddHit(uint32_t aScript, uint32_t aOffset, uint32_t aCheckpoint, + uint32_t aFrameIndex, ProgressCounter aProgress) { + ScriptHitKey key(aScript, aOffset); + ScriptHitMap::AddPtr p = mTable.lookupForAdd(key); + if (!p && !mTable.add(p, key, NewChunk(nullptr))) { + MOZ_CRASH("ScriptHitInfo::AddScriptHit"); + } + + ScriptHitChunk* chunk = p->value(); + p->value() = AddHit(chunk, ScriptHit(aCheckpoint, aFrameIndex, aProgress)); + } + + ScriptHitChunk* AddHit(ScriptHitChunk* aChunk, const ScriptHit& aHit) { + for (int i = ArrayLength(aChunk->mHits) - 1; i >= 0; i--) { + if (!aChunk->mHits[i].mCheckpoint) { + aChunk->mHits[i] = aHit; + return aChunk; + } + } + ScriptHitChunk* newChunk = NewChunk(aChunk); + newChunk->mHits[ArrayLength(newChunk->mHits) - 1] = aHit; + return newChunk; + } + + ScriptHitChunk* NewChunk(ScriptHitChunk* aPrevious) { + if (!mFreeChunk) { + void* mem = AllocateMemory(PageSize, MemoryKind::ScriptHits); + ScriptHitChunk* chunks = reinterpret_cast(mem); + size_t numChunks = PageSize / sizeof(ScriptHitChunk); + for (size_t i = 0; i < numChunks - 1; i++) { + chunks[i].mPrevious = &chunks[i + 1]; + } + mFreeChunk = chunks; + } + ScriptHitChunk* result = mFreeChunk; + mFreeChunk = mFreeChunk->mPrevious; + result->mPrevious = aPrevious; + return result; + } +}; + +static ScriptHitInfo* gScriptHits; + +static void InitializeScriptHits() { + void* mem = AllocateMemory(sizeof(ScriptHitInfo), MemoryKind::ScriptHits); + gScriptHits = new (mem) ScriptHitInfo(); +} + +static bool RecordReplay_AddScriptHit(JSContext* aCx, unsigned aArgc, + Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + + if (!args.get(0).isNumber() || + !args.get(1).isNumber() || + !args.get(2).isNumber()) { + JS_ReportErrorASCII(aCx, "Bad parameters"); + return false; + } + + uint32_t script = args.get(0).toNumber(); + uint32_t offset = args.get(1).toNumber(); + uint32_t frameIndex = args.get(2).toNumber(); + + gScriptHits->AddHit(script, offset, GetLastCheckpoint(), frameIndex, + gProgressCounter); + args.rval().setUndefined(); + return true; +} + +static bool RecordReplay_FindScriptHits(JSContext* aCx, unsigned aArgc, + Value* aVp) { + CallArgs args = CallArgsFromVp(aArgc, aVp); + + if (!args.get(0).isNumber() || !args.get(1).isNumber()) { + JS_ReportErrorASCII(aCx, "Bad parameters"); + return false; + } + + uint32_t script = args.get(0).toNumber(); + uint32_t offset = args.get(1).toNumber(); + + RootedValueVector values(aCx); + + ScriptHitInfo::ScriptHitChunk* chunk = + gScriptHits ? gScriptHits->FindHits(script, offset) : nullptr; + while (chunk) { + for (const auto& hit : chunk->mHits) { + if (hit.mCheckpoint) { + RootedObject hitObject(aCx, JS_NewObject(aCx, nullptr)); + if (!hitObject || + !JS_DefineProperty(aCx, hitObject, "checkpoint", + hit.mCheckpoint, JSPROP_ENUMERATE) || + !JS_DefineProperty(aCx, hitObject, "progress", + (double) hit.mProgress, JSPROP_ENUMERATE) || + !JS_DefineProperty(aCx, hitObject, "frameIndex", + hit.mFrameIndex, JSPROP_ENUMERATE) || + !values.append(ObjectValue(*hitObject))) { + return false; + } + } + } + chunk = chunk->mPrevious; + } + + JSObject* array = JS_NewArrayObject(aCx, values); + if (!array) { + return false; + } + + args.rval().setObject(*array); + return true; +} + static bool RecordReplay_GetContent(JSContext* aCx, unsigned aArgc, Value* aVp) { CallArgs args = CallArgsFromVp(aArgc, aVp); @@ -1065,67 +954,6 @@ static bool RecordReplay_GetContent(JSContext* aCx, unsigned aArgc, return true; } -static bool RecordReplay_CurrentExecutionPoint(JSContext* aCx, unsigned aArgc, - Value* aVp) { - CallArgs args = CallArgsFromVp(aArgc, aVp); - - Maybe position; - if (!args.get(0).isUndefined()) { - RootedObject obj(aCx, NonNullObject(aCx, args.get(0))); - if (!obj) { - return false; - } - - position.emplace(); - if (!position.ref().Decode(aCx, obj)) { - return false; - } - } - - ExecutionPoint point = navigation::CurrentExecutionPoint(position); - RootedObject result(aCx, point.Encode(aCx)); - if (!result) { - return false; - } - - args.rval().setObject(*result); - return true; -} - -static bool RecordReplay_TimeWarpTargetExecutionPoint(JSContext* aCx, - unsigned aArgc, - Value* aVp) { - CallArgs args = CallArgsFromVp(aArgc, aVp); - double timeWarpTarget; - if (!ToNumber(aCx, args.get(0), &timeWarpTarget)) { - return false; - } - - ExecutionPoint point = - navigation::TimeWarpTargetExecutionPoint((ProgressCounter)timeWarpTarget); - RootedObject result(aCx, point.Encode(aCx)); - if (!result) { - return false; - } - - args.rval().setObject(*result); - return true; -} - -static bool RecordReplay_RecordingEndpoint(JSContext* aCx, unsigned aArgc, - Value* aVp) { - CallArgs args = CallArgsFromVp(aArgc, aVp); - - ExecutionPoint point = navigation::GetRecordingEndpoint(); - RootedObject result(aCx, point.Encode(aCx)); - if (!result) { - return false; - } - - args.rval().setObject(*result); - return true; -} - static bool RecordReplay_Repaint(JSContext* aCx, unsigned aArgc, Value* aVp) { CallArgs args = CallArgsFromVp(aArgc, aVp); @@ -1172,36 +1000,31 @@ static const JSFunctionSpec gMiddlemanMethods[] = { JS_FN("registerReplayDebugger", Middleman_RegisterReplayDebugger, 1, 0), JS_FN("canRewind", Middleman_CanRewind, 0, 0), JS_FN("spawnReplayingChild", Middleman_SpawnReplayingChild, 0, 0), - JS_FN("setActiveChild", Middleman_SetActiveChild, 1, 0), - JS_FN("sendSetSaveCheckpoint", Middleman_SendSetSaveCheckpoint, 3, 0), - JS_FN("sendFlushRecording", Middleman_SendFlushRecording, 1, 0), - JS_FN("sendResume", Middleman_SendResume, 2, 0), - JS_FN("sendRestoreCheckpoint", Middleman_SendRestoreCheckpoint, 2, 0), - JS_FN("sendRunToPoint", Middleman_SendRunToPoint, 2, 0), - JS_FN("sendDebuggerRequest", Middleman_SendDebuggerRequest, 2, 0), - JS_FN("sendAddBreakpoint", Middleman_SendAddBreakpoint, 2, 0), - JS_FN("sendClearBreakpoints", Middleman_SendClearBreakpoints, 1, 0), + JS_FN("sendManifest", Middleman_SendManifest, 2, 0), JS_FN("hadRepaint", Middleman_HadRepaint, 2, 0), JS_FN("hadRepaintFailure", Middleman_HadRepaintFailure, 0, 0), JS_FN("inRepaintStressMode", Middleman_InRepaintStressMode, 0, 0), JS_FN("waitUntilPaused", Middleman_WaitUntilPaused, 1, 0), - JS_FN("positionSubsumes", Middleman_PositionSubsumes, 2, 0), JS_FS_END}; static const JSFunctionSpec gRecordReplayMethods[] = { JS_FN("areThreadEventsDisallowed", RecordReplay_AreThreadEventsDisallowed, 0, 0), - JS_FN("maybeDivergeFromRecording", RecordReplay_MaybeDivergeFromRecording, - 0, 0), + JS_FN("divergeFromRecording", RecordReplay_DivergeFromRecording, 0, 0), + JS_FN("progressCounter", RecordReplay_ProgressCounter, 0, 0), JS_FN("advanceProgressCounter", RecordReplay_AdvanceProgressCounter, 0, 0), JS_FN("shouldUpdateProgressCounter", RecordReplay_ShouldUpdateProgressCounter, 1, 0), - JS_FN("positionHit", RecordReplay_PositionHit, 1, 0), + JS_FN("manifestFinished", RecordReplay_ManifestFinished, 1, 0), + JS_FN("resumeExecution", RecordReplay_ResumeExecution, 0, 0), + JS_FN("restoreCheckpoint", RecordReplay_RestoreCheckpoint, 1, 0), + JS_FN("currentExecutionTime", RecordReplay_CurrentExecutionTime, 0, 0), + JS_FN("flushRecording", RecordReplay_FlushRecording, 0, 0), + JS_FN("setMainChild", RecordReplay_SetMainChild, 0, 0), + JS_FN("saveCheckpoint", RecordReplay_SaveCheckpoint, 1, 0), + JS_FN("addScriptHit", RecordReplay_AddScriptHit, 3, 0), + JS_FN("findScriptHits", RecordReplay_FindScriptHits, 2, 0), JS_FN("getContent", RecordReplay_GetContent, 1, 0), - JS_FN("currentExecutionPoint", RecordReplay_CurrentExecutionPoint, 1, 0), - JS_FN("timeWarpTargetExecutionPoint", - RecordReplay_TimeWarpTargetExecutionPoint, 1, 0), - JS_FN("recordingEndpoint", RecordReplay_RecordingEndpoint, 0, 0), JS_FN("repaint", RecordReplay_Repaint, 0, 0), JS_FN("dump", RecordReplay_Dump, 1, 0), JS_FS_END}; diff --git a/toolkit/recordreplay/ipc/JSControl.h b/toolkit/recordreplay/ipc/JSControl.h index 19678aea654b..621033c00de4 100644 --- a/toolkit/recordreplay/ipc/JSControl.h +++ b/toolkit/recordreplay/ipc/JSControl.h @@ -19,7 +19,10 @@ namespace mozilla { namespace recordreplay { struct Message; -struct HitExecutionPointMessage; + +namespace parent { + class ChildProcessInfo; +} namespace js { @@ -35,162 +38,6 @@ namespace js { // The RecordReplayControl object is also provided here, but has a different // interface which allows the JS to query the current process. -// Identification for a position where a breakpoint can be installed in a child -// process. Breakpoint positions describe all places between checkpoints where -// the child process can pause and be inspected by the middleman. A particular -// BreakpointPosition can be reached any number of times during execution of -// the process. -struct BreakpointPosition { - // clang-format off - MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(Kind, ( - Invalid, - - // Break at a script offset. Requires script/offset. - Break, - - // Break for an on-step handler within a frame. - // Requires script/offset/frameIndex. - OnStep, - - // Break either when any frame is popped, or when a specific frame is - // popped. Requires script/frameIndex in the latter case. - OnPop, - - // Break when entering any frame. - EnterFrame, - - // Break when a new top-level script is created. - NewScript, - - // Break when a message is logged to the web console. - ConsoleMessage, - - // Break when NewTimeWarpTarget() is called. - WarpTarget - )); - // clang-format on - - Kind mKind; - - // Optional information associated with the breakpoint. - uint32_t mScript; - uint32_t mOffset; - uint32_t mFrameIndex; - - static const uint32_t EMPTY_SCRIPT = (uint32_t)-1; - static const uint32_t EMPTY_OFFSET = (uint32_t)-1; - static const uint32_t EMPTY_FRAME_INDEX = (uint32_t)-1; - - BreakpointPosition() - : mKind(Invalid), - mScript(EMPTY_SCRIPT), - mOffset(EMPTY_OFFSET), - mFrameIndex(EMPTY_FRAME_INDEX) {} - - explicit BreakpointPosition(Kind aKind, uint32_t aScript = EMPTY_SCRIPT, - uint32_t aOffset = EMPTY_OFFSET, - uint32_t aFrameIndex = EMPTY_FRAME_INDEX) - : mKind(aKind), - mScript(aScript), - mOffset(aOffset), - mFrameIndex(aFrameIndex) {} - - bool IsValid() const { return mKind != Invalid; } - - inline bool operator==(const BreakpointPosition& o) const { - return mKind == o.mKind && mScript == o.mScript && mOffset == o.mOffset && - mFrameIndex == o.mFrameIndex; - } - - inline bool operator!=(const BreakpointPosition& o) const { - return !(*this == o); - } - - // Return whether an execution point matching |o| also matches this. - inline bool Subsumes(const BreakpointPosition& o) const { - return (*this == o) || - (mKind == OnPop && o.mKind == OnPop && mScript == EMPTY_SCRIPT) || - (mKind == Break && o.mKind == OnStep && mScript == o.mScript && - mOffset == o.mOffset); - } - - static const char* StaticKindString(Kind aKind) { - switch (aKind) { - case Invalid: - return "Invalid"; - case Break: - return "Break"; - case OnStep: - return "OnStep"; - case OnPop: - return "OnPop"; - case EnterFrame: - return "EnterFrame"; - case NewScript: - return "NewScript"; - case ConsoleMessage: - return "ConsoleMessage"; - case WarpTarget: - return "WarpTarget"; - } - MOZ_CRASH("Bad BreakpointPosition kind"); - } - - const char* KindString() const { return StaticKindString(mKind); } - - JSObject* Encode(JSContext* aCx) const; - bool Decode(JSContext* aCx, JS::HandleObject aObject); - void ToString(nsCString& aStr) const; -}; - -// Identification for a point in the execution of a child process where it may -// pause and be inspected by the middleman. A particular execution point will -// be reached exactly once during the execution of the process. -struct ExecutionPoint { - // ID of the last normal checkpoint prior to this point. - size_t mCheckpoint; - - // How much progress execution has made prior to reaching the point. - // A given BreakpointPosition may not be reached twice without an intervening - // increment of the global progress counter. - ProgressCounter mProgress; - - // The position reached after making the specified amount of progress, - // invalid if the execution point refers to the checkpoint itself. - BreakpointPosition mPosition; - - ExecutionPoint() : mCheckpoint(CheckpointId::Invalid), mProgress(0) {} - - ExecutionPoint(size_t aCheckpoint, ProgressCounter aProgress) - : mCheckpoint(aCheckpoint), mProgress(aProgress) {} - - ExecutionPoint(size_t aCheckpoint, ProgressCounter aProgress, - const BreakpointPosition& aPosition) - : mCheckpoint(aCheckpoint), mProgress(aProgress), mPosition(aPosition) { - // ExecutionPoint positions must be as precise as possible, and cannot - // subsume other positions. - MOZ_RELEASE_ASSERT(aPosition.IsValid()); - MOZ_RELEASE_ASSERT(aPosition.mKind != BreakpointPosition::OnPop || - aPosition.mScript != BreakpointPosition::EMPTY_SCRIPT); - MOZ_RELEASE_ASSERT(aPosition.mKind != BreakpointPosition::Break); - } - - bool HasPosition() const { return mPosition.IsValid(); } - - inline bool operator==(const ExecutionPoint& o) const { - return mCheckpoint == o.mCheckpoint && mProgress == o.mProgress && - mPosition == o.mPosition; - } - - inline bool operator!=(const ExecutionPoint& o) const { - return !(*this == o); - } - - JSObject* Encode(JSContext* aCx) const; - bool Decode(JSContext* aCx, JS::HandleObject aObject); - void ToString(nsCString& aStr) const; -}; - // Buffer type used for encoding object data. typedef InfallibleVector CharBuffer; @@ -198,6 +45,12 @@ typedef InfallibleVector CharBuffer; // its target script. void SetupDevtoolsSandbox(); +// JS state is initialized when the first checkpoint is reached. +bool IsInitialized(); + +// Start processing a manifest received from the middleman. +void ManifestStart(const CharBuffer& aContents); + // The following hooks are used in the middleman process to call methods defined // by the middleman control logic. @@ -205,8 +58,8 @@ void SetupDevtoolsSandbox(); void SetupMiddlemanControl(const Maybe& aRecordingChildId); // Handle an incoming message from a child process. -void ForwardHitExecutionPointMessage(size_t aId, - const HitExecutionPointMessage& aMsg); +void ForwardManifestFinished(parent::ChildProcessInfo* aChild, + const Message& aMsg); // Prepare the child processes so that the recording file can be safely copied. void BeforeSaveRecording(); @@ -215,27 +68,20 @@ void AfterSaveRecording(); // The following hooks are used in the recording/replaying process to // call methods defined by the JS sandbox. -// Handle an incoming request from the middleman. -void ProcessRequest(const char16_t* aRequest, size_t aRequestLength, - CharBuffer* aResponse); +// Called when running forward, immediately before hitting a normal or +// temporary checkpoint. +void BeforeCheckpoint(); -// Ensure there is a handler in place that will call -// RecordReplayControl.positionHit whenever the specified execution position is -// reached. -void EnsurePositionHandler(const BreakpointPosition& aPosition); +// Called immediately after hitting a normal or temporary checkpoint, either +// when running forward or immediately after rewinding. aRestoredCheckpoint is +// true if we just rewound. +void AfterCheckpoint(size_t aCheckpoint, bool aRestoredCheckpoint); -// Clear all installed position handlers. -void ClearPositionHandlers(); +// Accessors for state which can be accessed from JS. -// Clear all state that is kept while execution is paused. -void ClearPausedState(); - -// Given an execution position inside a script, get an execution position for -// the entry point of that script, otherwise return nothing. -Maybe GetEntryPosition(const BreakpointPosition& aPosition); - -// Called after receiving a DebuggerResponse from a child. -void OnDebuggerResponse(const Message& aMsg); +// Mark a time span when the main thread is idle. +void BeginIdleTime(); +void EndIdleTime(); } // namespace js } // namespace recordreplay diff --git a/toolkit/recordreplay/ipc/ParentForwarding.cpp b/toolkit/recordreplay/ipc/ParentForwarding.cpp index 2323815aec4b..d5ead7d0013b 100644 --- a/toolkit/recordreplay/ipc/ParentForwarding.cpp +++ b/toolkit/recordreplay/ipc/ParentForwarding.cpp @@ -275,8 +275,7 @@ class MiddlemanProtocol : public ipc::IToplevelProtocol { if (mSide == ipc::ChildSide) { AutoMarkMainThreadWaitingForIPDLReply blocked; while (!aReply) { - GetActiveChild()->WaitUntilPaused(); - GetActiveChild()->SendMessage(ResumeMessage(/* aForward = */ true)); + MOZ_CRASH("NYI"); } } else { MonitorAutoLock lock(*gMonitor); @@ -319,8 +318,7 @@ class MiddlemanProtocol : public ipc::IToplevelProtocol { if (mSide == ipc::ChildSide) { AutoMarkMainThreadWaitingForIPDLReply blocked; while (!aReply) { - GetActiveChild()->WaitUntilPaused(); - GetActiveChild()->SendMessage(ResumeMessage(/* aForward = */ true)); + MOZ_CRASH("NYI"); } } else { MonitorAutoLock lock(*gMonitor); diff --git a/toolkit/recordreplay/ipc/ParentGraphics.cpp b/toolkit/recordreplay/ipc/ParentGraphics.cpp index 5a9546289173..f549196026c8 100644 --- a/toolkit/recordreplay/ipc/ParentGraphics.cpp +++ b/toolkit/recordreplay/ipc/ParentGraphics.cpp @@ -104,14 +104,6 @@ static size_t gLastPaintWidth, gLastPaintHeight; // its graphics, we wait until both the Paint and the checkpoint itself have // been hit, with no intervening repaint. -// The last explicit paint message received from the child, if there has not -// been an intervening repaint. -static UniquePtr gLastExplicitPaint; - -// The last checkpoint the child reached, if there has not been an intervening -// repaint. -static size_t gLastCheckpoint; - void UpdateGraphicsInUIProcess(const PaintMessage* aMsg) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); @@ -126,10 +118,6 @@ void UpdateGraphicsInUIProcess(const PaintMessage* aMsg) { bool hadFailure = !aMsg; - // Clear out the last explicit paint information. This may delete aMsg. - gLastExplicitPaint = nullptr; - gLastCheckpoint = CheckpointId::Invalid; - // Make sure there is a sandbox which is running the graphics JS module. if (!gGraphics) { InitGraphicsSandbox(); @@ -187,23 +175,6 @@ void UpdateGraphicsInUIProcess(const PaintMessage* aMsg) { MOZ_ALWAYS_TRUE(JS::DetachArrayBuffer(cx, bufferObject)); } -static void MaybeTriggerExplicitPaint() { - if (gLastExplicitPaint && - gLastExplicitPaint->mCheckpointId == gLastCheckpoint) { - UpdateGraphicsInUIProcess(gLastExplicitPaint.get()); - } -} - -void MaybeUpdateGraphicsAtPaint(const PaintMessage& aMsg) { - gLastExplicitPaint.reset(new PaintMessage(aMsg)); - MaybeTriggerExplicitPaint(); -} - -void MaybeUpdateGraphicsAtCheckpoint(size_t aCheckpointId) { - gLastCheckpoint = aCheckpointId; - MaybeTriggerExplicitPaint(); -} - bool InRepaintStressMode() { static bool checked = false; static bool rv; diff --git a/toolkit/recordreplay/ipc/ParentIPC.cpp b/toolkit/recordreplay/ipc/ParentIPC.cpp index e5be5bba2f1e..3aca13b4c324 100644 --- a/toolkit/recordreplay/ipc/ParentIPC.cpp +++ b/toolkit/recordreplay/ipc/ParentIPC.cpp @@ -89,18 +89,6 @@ size_t SpawnReplayingChild() { return child->GetId(); } -void SetActiveChild(ChildProcessInfo* aChild) { - MOZ_RELEASE_ASSERT(aChild->IsPaused()); - - if (gActiveChild) { - MOZ_RELEASE_ASSERT(gActiveChild->IsPaused()); - gActiveChild->SendMessage(SetIsActiveMessage(false)); - } - - aChild->SendMessage(SetIsActiveMessage(true)); - gActiveChild = aChild; -} - void ResumeBeforeWaitingForIPDLReply() { MOZ_RELEASE_ASSERT(gActiveChild->IsRecording()); @@ -108,7 +96,7 @@ void ResumeBeforeWaitingForIPDLReply() { // recording child process. If the child is paused, resume it immediately so // that we don't deadlock. if (gActiveChild->IsPaused()) { - gActiveChild->SendMessage(ResumeMessage(/* aForward = */ true)); + MOZ_CRASH("NYI"); } } diff --git a/toolkit/recordreplay/ipc/ParentInternal.h b/toolkit/recordreplay/ipc/ParentInternal.h index 4646a0a632c0..943a324c21e7 100644 --- a/toolkit/recordreplay/ipc/ParentInternal.h +++ b/toolkit/recordreplay/ipc/ParentInternal.h @@ -76,11 +76,6 @@ void SendGraphicsMemoryToChild(); // an unhandled recording divergence. void UpdateGraphicsInUIProcess(const PaintMessage* aMsg); -// If necessary, update graphics after the active child sends a paint message -// or reaches a checkpoint. -void MaybeUpdateGraphicsAtPaint(const PaintMessage& aMsg); -void MaybeUpdateGraphicsAtCheckpoint(size_t aCheckpointId); - // ID for the mach message sent from a child process to the middleman to // request a port for the graphics shmem. static const int32_t GraphicsHandshakeMessageId = 42; @@ -141,7 +136,7 @@ class ChildProcessInfo { bool mHasBegunFatalError; bool mHasFatalError; - void OnIncomingMessage(const Message& aMsg, bool aForwardToControl); + void OnIncomingMessage(const Message& aMsg); static void MaybeProcessPendingMessageRunnable(); void ReceiveChildMessageOnMainThread(Message::UniquePtr aMsg); @@ -164,9 +159,8 @@ class ChildProcessInfo { // Handle incoming messages from this process (and no others) until it pauses. // The return value is null if it is already paused, otherwise the message - // which caused it to pause. In the latter case, OnIncomingMessage will *not* - // be called with the message. - Message::UniquePtr WaitUntilPaused(); + // which caused it to pause. + void WaitUntilPaused(); static void SetIntroductionMessage(IntroductionMessage* aMessage); }; diff --git a/toolkit/recordreplay/moz.build b/toolkit/recordreplay/moz.build index 8dd56716b4d5..249db3929130 100644 --- a/toolkit/recordreplay/moz.build +++ b/toolkit/recordreplay/moz.build @@ -18,7 +18,6 @@ if CONFIG['OS_ARCH'] == 'Darwin' and CONFIG['NIGHTLY_BUILD']: 'HashTable.cpp', 'ipc/Channel.cpp', 'ipc/ChildIPC.cpp', - 'ipc/ChildNavigation.cpp', 'ipc/ChildProcess.cpp', 'ipc/JSControl.cpp', 'ipc/ParentForwarding.cpp',