Bug 1573938 - Never collect wrapper JSObjects when recording/replaying, r=mccr8.

Differential Revision: https://phabricator.services.mozilla.com/D42011

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Brian Hackett 2019-08-16 20:51:12 +00:00
Родитель f74ee91093
Коммит 091f2992d2
20 изменённых файлов: 55 добавлений и 527 удалений

Просмотреть файл

@ -39,9 +39,10 @@ void nsWrapperCache::SetWrapperJSObject(JSObject* aWrapper) {
CycleCollectedJSRuntime::Get()->NurseryWrapperAdded(this);
}
if (mozilla::recordreplay::IsReplaying()) {
mozilla::recordreplay::SetWeakPointerJSRoot(this, aWrapper);
}
// Never collect the wrapper object while recording or replaying, to avoid
// non-deterministic behaviors if the cache is emptied and then refilled at
// a different point when replaying.
recordreplay::HoldJSObject(aWrapper);
}
void nsWrapperCache::ReleaseWrapper(void* aScriptObjectHolder) {
@ -98,12 +99,6 @@ static void DebugWrapperTraceCallback(JS::GCCellPtr aPtr, const char* aName,
void nsWrapperCache::CheckCCWrapperTraversal(void* aScriptObjectHolder,
nsScriptObjectTracer* aTracer) {
// Skip checking if we are recording or replaying, as calling
// GetWrapperPreserveColor() can cause the cache's wrapper to be cleared.
if (recordreplay::IsRecordingOrReplaying()) {
return;
}
JSObject* wrapper = GetWrapperPreserveColor();
if (!wrapper) {
return;

Просмотреть файл

@ -82,10 +82,6 @@ static_assert(sizeof(void*) == 4, "Only support 32-bit and 64-bit");
* A number of the methods are implemented in nsWrapperCacheInlines.h because we
* have to include some JS headers that don't play nicely with the rest of the
* codebase. Include nsWrapperCacheInlines.h if you need to call those methods.
*
* When recording or replaying an execution, wrapper caches are instrumented so
* that they behave consistently even if the GC executes at different points
* and collects different objects.
*/
class nsWrapperCache {
@ -102,10 +98,6 @@ class nsWrapperCache {
{
}
~nsWrapperCache() {
// Clear any JS root associated with this cache while replaying.
if (mozilla::recordreplay::IsReplaying()) {
mozilla::recordreplay::SetWeakPointerJSRoot(this, nullptr);
}
// Preserved wrappers should never end up getting cleared, but this can
// happen during shutdown when a leaked wrapper object is finalized, causing
// its wrapper to be cleared.
@ -145,23 +137,6 @@ class nsWrapperCache {
* escape.
*/
JSObject* GetWrapperMaybeDead() const {
// Keep track of accesses on the cache when recording or replaying an
// execution. Accesses during a GC (when thread events are disallowed)
// fetch the underlying object without making sure the returned value
// is consistent between recording and replay.
if (mozilla::recordreplay::IsRecordingOrReplaying() &&
!mozilla::recordreplay::AreThreadEventsDisallowed() &&
!mozilla::recordreplay::HasDivergedFromRecording()) {
bool success = mozilla::recordreplay::RecordReplayValue(!!mWrapper);
if (mozilla::recordreplay::IsReplaying()) {
if (success) {
MOZ_RELEASE_ASSERT(mWrapper);
} else {
const_cast<nsWrapperCache*>(this)->ClearWrapper();
}
}
}
return mWrapper;
}

Просмотреть файл

@ -1368,18 +1368,9 @@ inline mozilla::dom::ReflectionScope GetReflectionScope(
template <class T>
inline void ClearWrapper(T* p, nsWrapperCache* cache, JSObject* obj) {
// Skip clearing the wrapper when replaying. This method is called during
// finalization of |obj|, and when replaying a strong reference is kept on
// the contents of the cache: since |obj| is being finalized, the cache
// cannot point to |obj|, and clearing here won't do anything.
// Additionally, the reference held on the cache may have already been
// released, if we are finalizing later than we did while recording, and the
// cache may have already been deleted.
if (!recordreplay::IsReplaying()) {
MOZ_ASSERT(cache->GetWrapperMaybeDead() == obj ||
(js::RuntimeIsBeingDestroyed() && !cache->GetWrapperMaybeDead()));
cache->ClearWrapper(obj);
}
MOZ_ASSERT(cache->GetWrapperMaybeDead() == obj ||
(js::RuntimeIsBeingDestroyed() && !cache->GetWrapperMaybeDead()));
cache->ClearWrapper(obj);
}
template <class T>
@ -1387,13 +1378,9 @@ inline void ClearWrapper(T* p, void*, JSObject* obj) {
// QueryInterface to nsWrapperCache can't GC, we hope.
JS::AutoSuppressGCAnalysis nogc;
// Skip clearing the wrapper when replaying, for the same reason as in the
// overload above: |p| may have been deleted and we cannot QI it.
if (!recordreplay::IsReplaying()) {
nsWrapperCache* cache;
CallQueryInterface(p, &cache);
ClearWrapper(p, cache, obj);
}
nsWrapperCache* cache;
CallQueryInterface(p, &cache);
ClearWrapper(p, cache, obj);
}
template <class T>
@ -2562,11 +2549,6 @@ bool ToSupportsIsOnPrimaryInheritanceChain(T* aObject, nsWrapperCache* aCache) {
// object types.
inline size_t BindingJSObjectMallocBytes(void* aNativePtr) { return 0; }
// Register a thing which DeferredFinalize might be called on during GC
// finalization. See DeferredFinalize.h
template <class T>
static void RecordReplayRegisterDeferredFinalize(T* aObject);
// The BindingJSObjectCreator class is supposed to be used by a caller that
// wants to create and initialise a binding JSObject. After initialisation has
// been successfully completed it should call ForgetObject().
@ -2632,7 +2614,10 @@ class MOZ_STACK_CLASS BindingJSObjectCreator {
void InitializationSucceeded() {
T* pointer;
mNative.forget(&pointer);
RecordReplayRegisterDeferredFinalize<T>(pointer);
// Never collect binding objects while recording or replaying, to avoid
// non-deterministically releasing references during finalization.
recordreplay::HoldJSObject(mReflector);
mReflector = nullptr;
}
@ -2727,12 +2712,6 @@ struct DeferredFinalizer {
DeferredFinalize(Impl::AppendDeferredFinalizePointer,
Impl::DeferredFinalize, aObject);
}
static void RecordReplayRegisterDeferredFinalize(T* aObject) {
typedef DeferredFinalizerImpl<T> Impl;
RecordReplayRegisterDeferredFinalizeThing(
Impl::AppendDeferredFinalizePointer, Impl::DeferredFinalize, aObject);
}
};
template <class T>
@ -2740,10 +2719,6 @@ struct DeferredFinalizer<T, true> {
static void AddForDeferredFinalization(T* aObject) {
DeferredFinalize(reinterpret_cast<nsISupports*>(aObject));
}
static void RecordReplayRegisterDeferredFinalize(T* aObject) {
RecordReplayRegisterDeferredFinalizeThing(nullptr, nullptr, aObject);
}
};
template <class T>
@ -2751,11 +2726,6 @@ static void AddForDeferredFinalization(T* aObject) {
DeferredFinalizer<T>::AddForDeferredFinalization(aObject);
}
template <class T>
static void RecordReplayRegisterDeferredFinalize(T* aObject) {
DeferredFinalizer<T>::RecordReplayRegisterDeferredFinalize(aObject);
}
// This returns T's CC participant if it participates in CC and does not inherit
// from nsISupports. Otherwise, it returns null. QI should be used to get the
// participant if T inherits from nsISupports.
@ -2865,7 +2835,6 @@ bool CreateGlobal(JSContext* aCx, T* aNative, nsWrapperCache* aCache,
NS_ADDREF(aNative);
aCache->SetWrapper(aGlobal);
RecordReplayRegisterDeferredFinalize<T>(aNative);
dom::AllocateProtoAndIfaceCache(
aGlobal, CreateGlobalOptions<T>::ProtoAndIfaceCacheKind);

Просмотреть файл

@ -863,9 +863,12 @@ nsresult nsXBLBinding::DoInitJSClass(JSContext* cx, JS::Handle<JSObject*> obj,
nsXBLDocumentInfo* docInfo = aProtoBinding->XBLDocumentInfo();
::JS_SetPrivate(proto, docInfo);
NS_ADDREF(docInfo);
RecordReplayRegisterDeferredFinalize(docInfo);
JS_SetReservedSlot(proto, 0, JS::PrivateValue(aProtoBinding));
// Don't collect the proto while recording/replaying, to avoid
// non-deterministically releasing the docInfo reference.
recordreplay::HoldJSObject(proto);
// Next, enter the realm of the property holder, wrap the proto, and
// stick it on.
JSAutoRealm ar3(cx, holder);

Просмотреть файл

@ -33,8 +33,11 @@ class SandboxPrivate : public nsIGlobalObject,
// The type used to cast to void needs to match the one in GetPrivate.
nsIScriptObjectPrincipal* sop =
static_cast<nsIScriptObjectPrincipal*>(sbp.forget().take());
mozilla::RecordReplayRegisterDeferredFinalizeThing(nullptr, nullptr, sop);
JS_SetPrivate(global, sop);
// Never collect the global while recording or replaying, so that the
// principal reference is not released at a non-deterministic point.
mozilla::recordreplay::HoldJSObject(global);
}
static SandboxPrivate* GetPrivate(JSObject* obj) {

Просмотреть файл

@ -486,8 +486,6 @@ XPCWrappedNative::XPCWrappedNative(already_AddRefed<nsISupports>&& aIdentity,
MOZ_ASSERT(NS_IsMainThread());
mIdentity = aIdentity;
RecordReplayRegisterDeferredFinalizeThing(nullptr, nullptr, mIdentity);
mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID);
MOZ_ASSERT(mMaybeProto, "bad ctor param");
@ -503,8 +501,6 @@ XPCWrappedNative::XPCWrappedNative(already_AddRefed<nsISupports>&& aIdentity,
MOZ_ASSERT(NS_IsMainThread());
mIdentity = aIdentity;
RecordReplayRegisterDeferredFinalizeThing(nullptr, nullptr, mIdentity);
mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID);
MOZ_ASSERT(aScope, "bad ctor param");
@ -526,13 +522,8 @@ void XPCWrappedNative::Destroy() {
#endif
if (mIdentity) {
// Either release mIdentity immediately or defer the release. When
// recording or replaying the release must always be deferred, so that
// DeferredFinalize matches the earlier call to
// RecordReplayRegisterDeferredFinalizeThing.
XPCJSRuntime* rt = GetRuntime();
if ((rt && rt->GetDoingFinalization()) ||
recordreplay::IsRecordingOrReplaying()) {
if (rt && rt->GetDoingFinalization()) {
DeferredFinalize(mIdentity.forget().take());
} else {
mIdentity = nullptr;
@ -555,6 +546,10 @@ inline void XPCWrappedNative::SetFlatJSObject(JSObject* object) {
mFlatJSObject = object;
mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID);
// Never collect the wrapper object while recording or replaying, to avoid
// non-deterministically releasing references during finalization.
recordreplay::HoldJSObject(object);
}
inline void XPCWrappedNative::UnsetFlatJSObject() {
@ -762,10 +757,8 @@ void XPCWrappedNative::FlatJSObjectFinalized() {
}
// We also need to release any native pointers held...
// As for XPCWrappedNative::Destroy, when recording or replaying the
// release must always be deferred.
RefPtr<nsISupports> native = to->TakeNative();
if (native && (GetRuntime() || recordreplay::IsRecordingOrReplaying())) {
if (native && GetRuntime()) {
DeferredFinalize(native.forget().take());
}
@ -1033,7 +1026,6 @@ nsresult XPCWrappedNative::InitTearOff(JSContext* cx,
aTearOff->SetInterface(aInterface);
aTearOff->SetNative(qiResult);
RecordReplayRegisterDeferredFinalizeThing(nullptr, nullptr, qiResult);
if (needJSObject && !InitTearOffJSObject(cx, aTearOff)) {
return NS_ERROR_OUT_OF_MEMORY;

Просмотреть файл

@ -28,8 +28,6 @@ XPCWrappedNativeProto::XPCWrappedNativeProto(
#ifdef DEBUG
gDEBUG_LiveProtoCount++;
#endif
RecordReplayRegisterDeferredFinalizeThing(nullptr, nullptr, mClassInfo);
}
XPCWrappedNativeProto::~XPCWrappedNativeProto() {
@ -57,6 +55,10 @@ bool XPCWrappedNativeProto::Init(JSContext* cx, nsIXPCScriptable* scriptable) {
bool success = !!mJSProtoObject;
if (success) {
JS_SetPrivate(mJSProtoObject, this);
// Never collect the proto object while recording or replaying, to avoid
// non-deterministically releasing references during finalization.
recordreplay::HoldJSObject(mJSProtoObject);
}
return success;

Просмотреть файл

@ -62,16 +62,8 @@ namespace recordreplay {
(const PLDHashTableOps* aFirstOps, \
const PLDHashTableOps* aSecondOps), \
(aFirstOps, aSecondOps)) \
Macro(SetWeakPointerJSRoot, \
(const void* aPtr, JSObject* aJSObj), (aPtr, aJSObj)) \
Macro(RegisterTrigger, \
(void* aObj, const std::function<void()>& aCallback), \
(aObj, \
aCallback)) Macro(UnregisterTrigger, (void* aObj), \
(aObj)) Macro(ActivateTrigger, \
(void* aObj), (aObj)) \
Macro(ExecuteTriggers, (), ()) Macro( \
InternalRecordReplayAssert, \
Macro(InternalHoldJSObject, (JSObject* aJSObj), (aJSObj)) \
Macro(InternalRecordReplayAssert, \
(const char* aFormat, va_list aArgs), \
(aFormat, \
aArgs)) Macro(InternalRecordReplayAssertBytes, \

Просмотреть файл

@ -187,54 +187,11 @@ static inline void DestroyPLDHashTableCallbacks(const PLDHashTableOps* aOps);
static inline void MovePLDHashTableContents(const PLDHashTableOps* aFirstOps,
const PLDHashTableOps* aSecondOps);
// Associate an arbitrary pointer with a JS object root while replaying. This
// is useful for replaying the behavior of weak pointers.
MFBT_API void SetWeakPointerJSRoot(const void* aPtr, JSObject* aJSObj);
// API for ensuring that a function executes at a consistent point when
// recording or replaying. This is primarily needed for finalizers and other
// activity during a GC that can perform recorded events (because GCs can
// occur at different times and behave differently between recording and
// replay, thread events are disallowed during a GC). Triggers can be
// registered at a point where thread events are allowed, then activated at
// a point where thread events are not allowed. When recording, the trigger's
// callback will execute at the next point when ExecuteTriggers is called on
// the thread which originally registered the trigger (typically at the top of
// the thread's event loop), and when replaying the callback will execute at
// the same point, even if it was never activated.
//
// Below is an example of how this API can be used.
//
// // This structure's lifetime is managed by the GC.
// struct GarbageCollectedHolder {
// GarbageCollectedHolder() {
// RegisterTrigger(this, [=]() { this->DestroyContents(); });
// }
// ~GarbageCollectedHolder() {
// UnregisterTrigger(this);
// }
//
// void Finalize() {
// // During finalization, thread events are disallowed.
// if (IsRecordingOrReplaying()) {
// ActivateTrigger(this);
// } else {
// DestroyContents();
// }
// }
//
// // This is free to release resources held by the system, communicate with
// // other threads or processes, and so forth. When replaying, this may
// // be called before the GC has actually collected this object, but since
// // the GC will have already collected this object at this point in the
// // recording, this object will never be accessed again.
// void DestroyContents();
// };
MFBT_API void RegisterTrigger(void* aObj,
const std::function<void()>& aCallback);
MFBT_API void UnregisterTrigger(void* aObj);
MFBT_API void ActivateTrigger(void* aObj);
MFBT_API void ExecuteTriggers();
// Prevent a JS object from ever being collected while recording or replaying.
// GC behavior is non-deterministic when recording/replaying, and preventing
// an object from being collected ensures that finalizers which might interact
// with the recording will not execute.
static inline void HoldJSObject(JSObject* aJSObj);
// Some devtools operations which execute in a replaying process can cause code
// to run which did not run while recording. For example, the JS debugger can
@ -419,15 +376,8 @@ MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(MovePLDHashTableContents,
(aFirstOps, aSecondOps))
MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(InvalidateRecording, (const char* aWhy),
(aWhy))
MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(
RegisterWeakPointer,
(const void* aPtr, const std::function<void(bool)>& aCallback),
(aPtr, aCallback))
MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(UnregisterWeakPointer, (const void* aPtr),
(aPtr))
MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(WeakPointerAccess,
(const void* aPtr, bool aSuccess),
(aPtr, aSuccess))
MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(HoldJSObject, (JSObject* aObject),
(aObject))
MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(RecordReplayAssertBytes,
(const void* aData, size_t aSize),
(aData, aSize))

Просмотреть файл

@ -7,6 +7,7 @@
#include "ProcessRecordReplay.h"
#include "ipc/ChildInternal.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/Compression.h"
#include "mozilla/Maybe.h"
#include "mozilla/Sprintf.h"
@ -16,9 +17,7 @@
#include "MemorySnapshot.h"
#include "ProcessRedirect.h"
#include "ProcessRewind.h"
#include "Trigger.h"
#include "ValueIndex.h"
#include "WeakPointer.h"
#include "pratom.h"
#include <fcntl.h>
@ -149,8 +148,6 @@ MOZ_EXPORT void RecordReplayInterface_Initialize(int aArgc, char* aArgv[]) {
thread->BindToCurrent();
thread->SetPassThrough(true);
InitializeTriggers();
InitializeWeakPointers();
InitializeMemorySnapshots();
Thread::SpawnAllThreads();
InitializeCountdownThread();
@ -392,6 +389,14 @@ MOZ_EXPORT const char* RecordReplayInterface_InternalVirtualThingName(
return name ? name : "(unknown)";
}
MOZ_EXPORT void RecordReplayInterface_InternalHoldJSObject(JSObject* aJSObj) {
if (aJSObj) {
JSContext* cx = dom::danger::GetJSContext();
JS::PersistentRootedObject* root = new JS::PersistentRootedObject(cx);
*root = aJSObj;
}
}
} // extern "C"
} // namespace recordreplay

Просмотреть файл

@ -55,16 +55,7 @@ namespace recordreplay {
_Macro(CallbacksFinished) \
\
/* Restoring a data pointer used in a callback (see Callback.h). */ \
_Macro(RestoreCallbackData) \
\
/* Called RegisterTrigger. */ \
_Macro(RegisterTrigger) \
\
/* Executed a trigger within a call to ExecuteTriggers. */ \
_Macro(ExecuteTrigger) \
\
/* Finished executing triggers within a call to ExecuteTriggers. */ \
_Macro(ExecuteTriggersFinished)
_Macro(RestoreCallbackData)
// ID of an event in a thread's event stream. Each ID in the stream is followed
// by data associated with the event.

Просмотреть файл

@ -1,189 +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 "Trigger.h"
#include "ipc/ChildIPC.h"
#include "mozilla/Maybe.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/RecordReplay.h"
#include "InfallibleVector.h"
#include "ProcessRewind.h"
#include "Thread.h"
#include "ValueIndex.h"
namespace mozilla {
namespace recordreplay {
// Information about each trigger.
struct TriggerInfo {
// ID of the thread which registered this trigger.
size_t mThreadId;
// Callback to execute when the trigger is activated.
std::function<void()> mCallback;
// Number of times this trigger has been activated.
size_t mRegisterCount;
TriggerInfo(size_t aThreadId, const std::function<void()>& aCallback)
: mThreadId(aThreadId), mCallback(aCallback), mRegisterCount(1) {}
};
// All registered triggers.
static ValueIndex* gTriggers;
typedef std::unordered_map<void*, TriggerInfo> TriggerInfoMap;
static TriggerInfoMap* gTriggerInfoMap;
// Triggers which have been activated. This is protected by the global lock.
static StaticInfallibleVector<size_t> gActivatedTriggers;
static StaticMutexNotRecorded gTriggersMutex;
void InitializeTriggers() {
gTriggers = new ValueIndex();
gTriggerInfoMap = new TriggerInfoMap();
}
extern "C" {
MOZ_EXPORT void RecordReplayInterface_RegisterTrigger(
void* aObj, const std::function<void()>& aCallback) {
MOZ_RELEASE_ASSERT(aObj);
MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
Thread* thread = Thread::Current();
if (thread->HasDivergedFromRecording()) {
return;
}
MOZ_RELEASE_ASSERT(thread->CanAccessRecording());
size_t id;
{
AutoOrderedAtomicAccess order(gTriggers);
StaticMutexAutoLock lock(gTriggersMutex);
TriggerInfoMap::iterator iter = gTriggerInfoMap->find(aObj);
if (iter != gTriggerInfoMap->end()) {
id = gTriggers->GetIndex(aObj);
MOZ_RELEASE_ASSERT(iter->second.mThreadId == thread->Id());
iter->second.mCallback = aCallback;
iter->second.mRegisterCount++;
} else {
id = gTriggers->Insert(aObj);
TriggerInfo info(thread->Id(), aCallback);
gTriggerInfoMap->insert(TriggerInfoMap::value_type(aObj, info));
}
}
RecordingEventSection res(thread);
MOZ_RELEASE_ASSERT(res.CanAccessEvents());
thread->Events().RecordOrReplayThreadEvent(ThreadEvent::RegisterTrigger);
thread->Events().CheckInput(id);
}
MOZ_EXPORT void RecordReplayInterface_UnregisterTrigger(void* aObj) {
MOZ_ASSERT(IsRecordingOrReplaying());
MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
StaticMutexAutoLock lock(gTriggersMutex);
TriggerInfoMap::iterator iter = gTriggerInfoMap->find(aObj);
MOZ_RELEASE_ASSERT(iter != gTriggerInfoMap->end());
if (--iter->second.mRegisterCount == 0) {
gTriggerInfoMap->erase(iter);
gTriggers->Remove(aObj);
}
}
MOZ_EXPORT void RecordReplayInterface_ActivateTrigger(void* aObj) {
if (!IsRecording()) {
return;
}
StaticMutexAutoLock lock(gTriggersMutex);
size_t id = gTriggers->GetIndex(aObj);
gActivatedTriggers.emplaceBack(id);
}
static void InvokeTriggerCallback(size_t aId) {
void* obj;
std::function<void()> callback;
{
StaticMutexAutoLock lock(gTriggersMutex);
obj = const_cast<void*>(gTriggers->GetValue(aId));
TriggerInfoMap::iterator iter = gTriggerInfoMap->find(obj);
MOZ_RELEASE_ASSERT(iter != gTriggerInfoMap->end());
MOZ_RELEASE_ASSERT(iter->second.mThreadId == Thread::Current()->Id());
MOZ_RELEASE_ASSERT(iter->second.mRegisterCount);
MOZ_RELEASE_ASSERT(iter->second.mCallback);
callback = iter->second.mCallback;
}
callback();
}
static Maybe<size_t> RemoveTriggerCallbackForThreadId(size_t aThreadId) {
StaticMutexAutoLock lock(gTriggersMutex);
for (size_t i = 0; i < gActivatedTriggers.length(); i++) {
size_t id = gActivatedTriggers[i];
void* obj = const_cast<void*>(gTriggers->GetValue(id));
TriggerInfoMap::iterator iter = gTriggerInfoMap->find(obj);
MOZ_RELEASE_ASSERT(iter != gTriggerInfoMap->end());
if (iter->second.mThreadId == aThreadId) {
gActivatedTriggers.erase(&gActivatedTriggers[i]);
return Some(id);
}
}
return Nothing();
}
MOZ_EXPORT void RecordReplayInterface_ExecuteTriggers() {
Thread* thread = Thread::Current();
RecordingEventSection res(thread);
if (!res.CanAccessEvents()) {
return;
}
if (IsRecording()) {
// Invoke the callbacks for any triggers waiting for execution, including
// any whose callbacks are triggered by earlier callback invocations.
while (true) {
Maybe<size_t> id = RemoveTriggerCallbackForThreadId(thread->Id());
if (id.isNothing()) {
break;
}
thread->Events().WriteScalar((size_t)ThreadEvent::ExecuteTrigger);
thread->Events().WriteScalar(id.ref());
InvokeTriggerCallback(id.ref());
}
thread->Events().WriteScalar((size_t)ThreadEvent::ExecuteTriggersFinished);
} else {
// Execute the same callbacks which were executed at this point while
// recording.
while (true) {
ThreadEvent ev = (ThreadEvent)thread->Events().ReadScalar();
if (ev != ThreadEvent::ExecuteTrigger) {
if (ev != ThreadEvent::ExecuteTriggersFinished) {
child::ReportFatalError(Nothing(), "ExecuteTrigger Mismatch");
Unreachable();
}
break;
}
size_t id = thread->Events().ReadScalar();
InvokeTriggerCallback(id);
}
}
}
} // extern "C"
} // namespace recordreplay
} // namespace mozilla

Просмотреть файл

@ -1,21 +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/. */
#ifndef mozilla_recordreplay_Trigger_h
#define mozilla_recordreplay_Trigger_h
namespace mozilla {
namespace recordreplay {
// See RecordReplay.h for a description of the record/replay trigger API.
// Initialize trigger state at the beginning of recording or replaying.
void InitializeTriggers();
} // namespace recordreplay
} // namespace mozilla
#endif // mozilla_recordreplay_Trigger_h

Просмотреть файл

@ -1,61 +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 "WeakPointer.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/StaticMutex.h"
#include "jsapi.h"
#include <unordered_map>
namespace mozilla {
namespace recordreplay {
typedef std::unordered_map<const void*, UniquePtr<JS::PersistentRootedObject>>
WeakPointerRootMap;
static WeakPointerRootMap* gWeakPointerRootMap;
static StaticMutexNotRecorded gWeakPointerMutex;
static UniquePtr<JS::PersistentRootedObject> NewRoot(JSObject* aJSObj) {
MOZ_RELEASE_ASSERT(aJSObj);
JSContext* cx = dom::danger::GetJSContext();
UniquePtr<JS::PersistentRootedObject> root =
MakeUnique<JS::PersistentRootedObject>(cx);
*root = aJSObj;
return root;
}
extern "C" {
MOZ_EXPORT void RecordReplayInterface_SetWeakPointerJSRoot(const void* aPtr,
JSObject* aJSObj) {
MOZ_RELEASE_ASSERT(IsReplaying());
StaticMutexAutoLock lock(gWeakPointerMutex);
auto iter = gWeakPointerRootMap->find(aPtr);
if (iter != gWeakPointerRootMap->end()) {
if (aJSObj) {
*iter->second = aJSObj;
} else {
gWeakPointerRootMap->erase(aPtr);
}
} else if (aJSObj) {
gWeakPointerRootMap->insert(
WeakPointerRootMap::value_type(aPtr, NewRoot(aJSObj)));
}
}
} // extern "C"
void InitializeWeakPointers() {
gWeakPointerRootMap = new WeakPointerRootMap();
}
} // namespace recordreplay
} // namespace mozilla

Просмотреть файл

@ -1,21 +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/. */
#ifndef mozilla_recordreplay_WeakPointer_h
#define mozilla_recordreplay_WeakPointer_h
namespace mozilla {
namespace recordreplay {
// See RecordReplay.h for a description of the record/replay weak pointer API.
// Initialize weak pointer state.
void InitializeWeakPointers();
} // namespace recordreplay
} // namespace mozilla
#endif // mozilla_recordreplay_WeakPointer_h

Просмотреть файл

@ -6,6 +6,7 @@
#include "JSControl.h"
#include "mozilla/Base64.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/StaticPtr.h"
#include "js/CharacterEncoding.h"
@ -13,6 +14,7 @@
#include "js/JSON.h"
#include "js/PropertySpec.h"
#include "ChildInternal.h"
#include "MemorySnapshot.h"
#include "ParentInternal.h"
#include "nsImportModule.h"
#include "rrIControl.h"

Просмотреть файл

@ -31,9 +31,7 @@ if CONFIG['OS_ARCH'] == 'Darwin' and CONFIG['NIGHTLY_BUILD']:
'ProcessRewind.cpp',
'Thread.cpp',
'ThreadSnapshot.cpp',
'Trigger.cpp',
'ValueIndex.cpp',
'WeakPointer.cpp',
]
SOURCES += [
# ProcessRedirect includes udis86 directly and will not compile if the

Просмотреть файл

@ -1358,13 +1358,6 @@ void CycleCollectedJSRuntime::FinalizeDeferredThings(
}
}
// When recording or replaying, execute triggers that were activated recently
// by mozilla::DeferredFinalize. This will populate the deferred finalizer
// table with a consistent set of entries between the recording and replay.
if (recordreplay::IsRecordingOrReplaying()) {
recordreplay::ExecuteTriggers();
}
if (mDeferredFinalizerTable.Count() == 0) {
return;
}

Просмотреть файл

@ -12,48 +12,12 @@
void mozilla::DeferredFinalize(nsISupports* aSupports) {
CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get();
MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now");
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
// RecordReplayRegisterDeferredFinalizeThing should have been called when
// the reference on this object was added earlier. Cause the reference to
// be released soon, at a consistent point in the recording and replay.
mozilla::recordreplay::ActivateTrigger(aSupports);
} else {
rt->DeferredFinalize(aSupports);
}
rt->DeferredFinalize(aSupports);
}
void mozilla::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
DeferredFinalizeFunction aFunc, void* aThing) {
CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get();
MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now");
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
// As above, cause the finalization action to occur soon, at a consistent
// point in the recording and replay.
mozilla::recordreplay::ActivateTrigger(aThing);
} else {
rt->DeferredFinalize(aAppendFunc, aFunc, aThing);
}
}
static void RecordReplayDeferredFinalize(
DeferredFinalizeAppendFunction aAppendFunc, DeferredFinalizeFunction aFunc,
void* aThing) {
mozilla::recordreplay::UnregisterTrigger(aThing);
CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get();
if (aAppendFunc) {
rt->DeferredFinalize(aAppendFunc, aFunc, aThing);
} else {
rt->DeferredFinalize(reinterpret_cast<nsISupports*>(aThing));
}
}
void mozilla::RecordReplayRegisterDeferredFinalizeThing(
DeferredFinalizeAppendFunction aAppendFunc, DeferredFinalizeFunction aFunc,
void* aThing) {
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
mozilla::recordreplay::RegisterTrigger(aThing, [=]() {
RecordReplayDeferredFinalize(aAppendFunc, aFunc, aThing);
});
}
rt->DeferredFinalize(aAppendFunc, aFunc, aThing);
}

Просмотреть файл

@ -27,20 +27,6 @@ void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
void DeferredFinalize(nsISupports* aSupports);
// When recording or replaying, deferred finalizers are forced to run at the
// same point during replay that they ran at while recording, even if there is
// a JSObject associated with the reference which has not been collected yet
// (since at this point the JSObject has been collected during the recording,
// that JSObject will never be used again and its reference can be released).
//
// This requires that RecordReplayRegisterDeferredFinalizeThing() be called for
// every thing which DeferredFinalize() will be called for at a later time.
// Calls to these functions must be 1:1. When not recording or replaying, this
// function is a no-op.
void RecordReplayRegisterDeferredFinalizeThing(
DeferredFinalizeAppendFunction aAppendFunc, DeferredFinalizeFunction aFunc,
void* aThing);
} // namespace mozilla
#endif // mozilla_DeferredFinalize_h