зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
f74ee91093
Коммит
091f2992d2
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче