mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-04-12 11:04:32 +00:00
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:
parent
f74ee91093
commit
091f2992d2
@ -39,9 +39,10 @@ void nsWrapperCache::SetWrapperJSObject(JSObject* aWrapper) {
|
|||||||
CycleCollectedJSRuntime::Get()->NurseryWrapperAdded(this);
|
CycleCollectedJSRuntime::Get()->NurseryWrapperAdded(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mozilla::recordreplay::IsReplaying()) {
|
// Never collect the wrapper object while recording or replaying, to avoid
|
||||||
mozilla::recordreplay::SetWeakPointerJSRoot(this, aWrapper);
|
// 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) {
|
void nsWrapperCache::ReleaseWrapper(void* aScriptObjectHolder) {
|
||||||
@ -98,12 +99,6 @@ static void DebugWrapperTraceCallback(JS::GCCellPtr aPtr, const char* aName,
|
|||||||
|
|
||||||
void nsWrapperCache::CheckCCWrapperTraversal(void* aScriptObjectHolder,
|
void nsWrapperCache::CheckCCWrapperTraversal(void* aScriptObjectHolder,
|
||||||
nsScriptObjectTracer* aTracer) {
|
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();
|
JSObject* wrapper = GetWrapperPreserveColor();
|
||||||
if (!wrapper) {
|
if (!wrapper) {
|
||||||
return;
|
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
|
* 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
|
* 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.
|
* 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 {
|
class nsWrapperCache {
|
||||||
@ -102,10 +98,6 @@ class nsWrapperCache {
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
~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
|
// Preserved wrappers should never end up getting cleared, but this can
|
||||||
// happen during shutdown when a leaked wrapper object is finalized, causing
|
// happen during shutdown when a leaked wrapper object is finalized, causing
|
||||||
// its wrapper to be cleared.
|
// its wrapper to be cleared.
|
||||||
@ -145,23 +137,6 @@ class nsWrapperCache {
|
|||||||
* escape.
|
* escape.
|
||||||
*/
|
*/
|
||||||
JSObject* GetWrapperMaybeDead() const {
|
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;
|
return mWrapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1368,18 +1368,9 @@ inline mozilla::dom::ReflectionScope GetReflectionScope(
|
|||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
inline void ClearWrapper(T* p, nsWrapperCache* cache, JSObject* obj) {
|
inline void ClearWrapper(T* p, nsWrapperCache* cache, JSObject* obj) {
|
||||||
// Skip clearing the wrapper when replaying. This method is called during
|
MOZ_ASSERT(cache->GetWrapperMaybeDead() == obj ||
|
||||||
// finalization of |obj|, and when replaying a strong reference is kept on
|
(js::RuntimeIsBeingDestroyed() && !cache->GetWrapperMaybeDead()));
|
||||||
// the contents of the cache: since |obj| is being finalized, the cache
|
cache->ClearWrapper(obj);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
@ -1387,13 +1378,9 @@ inline void ClearWrapper(T* p, void*, JSObject* obj) {
|
|||||||
// QueryInterface to nsWrapperCache can't GC, we hope.
|
// QueryInterface to nsWrapperCache can't GC, we hope.
|
||||||
JS::AutoSuppressGCAnalysis nogc;
|
JS::AutoSuppressGCAnalysis nogc;
|
||||||
|
|
||||||
// Skip clearing the wrapper when replaying, for the same reason as in the
|
nsWrapperCache* cache;
|
||||||
// overload above: |p| may have been deleted and we cannot QI it.
|
CallQueryInterface(p, &cache);
|
||||||
if (!recordreplay::IsReplaying()) {
|
ClearWrapper(p, cache, obj);
|
||||||
nsWrapperCache* cache;
|
|
||||||
CallQueryInterface(p, &cache);
|
|
||||||
ClearWrapper(p, cache, obj);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
@ -2562,11 +2549,6 @@ bool ToSupportsIsOnPrimaryInheritanceChain(T* aObject, nsWrapperCache* aCache) {
|
|||||||
// object types.
|
// object types.
|
||||||
inline size_t BindingJSObjectMallocBytes(void* aNativePtr) { return 0; }
|
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
|
// The BindingJSObjectCreator class is supposed to be used by a caller that
|
||||||
// wants to create and initialise a binding JSObject. After initialisation has
|
// wants to create and initialise a binding JSObject. After initialisation has
|
||||||
// been successfully completed it should call ForgetObject().
|
// been successfully completed it should call ForgetObject().
|
||||||
@ -2632,7 +2614,10 @@ class MOZ_STACK_CLASS BindingJSObjectCreator {
|
|||||||
void InitializationSucceeded() {
|
void InitializationSucceeded() {
|
||||||
T* pointer;
|
T* pointer;
|
||||||
mNative.forget(&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;
|
mReflector = nullptr;
|
||||||
}
|
}
|
||||||
@ -2727,12 +2712,6 @@ struct DeferredFinalizer {
|
|||||||
DeferredFinalize(Impl::AppendDeferredFinalizePointer,
|
DeferredFinalize(Impl::AppendDeferredFinalizePointer,
|
||||||
Impl::DeferredFinalize, aObject);
|
Impl::DeferredFinalize, aObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void RecordReplayRegisterDeferredFinalize(T* aObject) {
|
|
||||||
typedef DeferredFinalizerImpl<T> Impl;
|
|
||||||
RecordReplayRegisterDeferredFinalizeThing(
|
|
||||||
Impl::AppendDeferredFinalizePointer, Impl::DeferredFinalize, aObject);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
@ -2740,10 +2719,6 @@ struct DeferredFinalizer<T, true> {
|
|||||||
static void AddForDeferredFinalization(T* aObject) {
|
static void AddForDeferredFinalization(T* aObject) {
|
||||||
DeferredFinalize(reinterpret_cast<nsISupports*>(aObject));
|
DeferredFinalize(reinterpret_cast<nsISupports*>(aObject));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void RecordReplayRegisterDeferredFinalize(T* aObject) {
|
|
||||||
RecordReplayRegisterDeferredFinalizeThing(nullptr, nullptr, aObject);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
@ -2751,11 +2726,6 @@ static void AddForDeferredFinalization(T* aObject) {
|
|||||||
DeferredFinalizer<T>::AddForDeferredFinalization(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
|
// 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
|
// from nsISupports. Otherwise, it returns null. QI should be used to get the
|
||||||
// participant if T inherits from nsISupports.
|
// participant if T inherits from nsISupports.
|
||||||
@ -2865,7 +2835,6 @@ bool CreateGlobal(JSContext* aCx, T* aNative, nsWrapperCache* aCache,
|
|||||||
NS_ADDREF(aNative);
|
NS_ADDREF(aNative);
|
||||||
|
|
||||||
aCache->SetWrapper(aGlobal);
|
aCache->SetWrapper(aGlobal);
|
||||||
RecordReplayRegisterDeferredFinalize<T>(aNative);
|
|
||||||
|
|
||||||
dom::AllocateProtoAndIfaceCache(
|
dom::AllocateProtoAndIfaceCache(
|
||||||
aGlobal, CreateGlobalOptions<T>::ProtoAndIfaceCacheKind);
|
aGlobal, CreateGlobalOptions<T>::ProtoAndIfaceCacheKind);
|
||||||
|
@ -863,9 +863,12 @@ nsresult nsXBLBinding::DoInitJSClass(JSContext* cx, JS::Handle<JSObject*> obj,
|
|||||||
nsXBLDocumentInfo* docInfo = aProtoBinding->XBLDocumentInfo();
|
nsXBLDocumentInfo* docInfo = aProtoBinding->XBLDocumentInfo();
|
||||||
::JS_SetPrivate(proto, docInfo);
|
::JS_SetPrivate(proto, docInfo);
|
||||||
NS_ADDREF(docInfo);
|
NS_ADDREF(docInfo);
|
||||||
RecordReplayRegisterDeferredFinalize(docInfo);
|
|
||||||
JS_SetReservedSlot(proto, 0, JS::PrivateValue(aProtoBinding));
|
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
|
// Next, enter the realm of the property holder, wrap the proto, and
|
||||||
// stick it on.
|
// stick it on.
|
||||||
JSAutoRealm ar3(cx, holder);
|
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.
|
// The type used to cast to void needs to match the one in GetPrivate.
|
||||||
nsIScriptObjectPrincipal* sop =
|
nsIScriptObjectPrincipal* sop =
|
||||||
static_cast<nsIScriptObjectPrincipal*>(sbp.forget().take());
|
static_cast<nsIScriptObjectPrincipal*>(sbp.forget().take());
|
||||||
mozilla::RecordReplayRegisterDeferredFinalizeThing(nullptr, nullptr, sop);
|
|
||||||
JS_SetPrivate(global, 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) {
|
static SandboxPrivate* GetPrivate(JSObject* obj) {
|
||||||
|
@ -486,8 +486,6 @@ XPCWrappedNative::XPCWrappedNative(already_AddRefed<nsISupports>&& aIdentity,
|
|||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
||||||
mIdentity = aIdentity;
|
mIdentity = aIdentity;
|
||||||
RecordReplayRegisterDeferredFinalizeThing(nullptr, nullptr, mIdentity);
|
|
||||||
|
|
||||||
mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID);
|
mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID);
|
||||||
|
|
||||||
MOZ_ASSERT(mMaybeProto, "bad ctor param");
|
MOZ_ASSERT(mMaybeProto, "bad ctor param");
|
||||||
@ -503,8 +501,6 @@ XPCWrappedNative::XPCWrappedNative(already_AddRefed<nsISupports>&& aIdentity,
|
|||||||
MOZ_ASSERT(NS_IsMainThread());
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
||||||
mIdentity = aIdentity;
|
mIdentity = aIdentity;
|
||||||
RecordReplayRegisterDeferredFinalizeThing(nullptr, nullptr, mIdentity);
|
|
||||||
|
|
||||||
mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID);
|
mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID);
|
||||||
|
|
||||||
MOZ_ASSERT(aScope, "bad ctor param");
|
MOZ_ASSERT(aScope, "bad ctor param");
|
||||||
@ -526,13 +522,8 @@ void XPCWrappedNative::Destroy() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (mIdentity) {
|
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();
|
XPCJSRuntime* rt = GetRuntime();
|
||||||
if ((rt && rt->GetDoingFinalization()) ||
|
if (rt && rt->GetDoingFinalization()) {
|
||||||
recordreplay::IsRecordingOrReplaying()) {
|
|
||||||
DeferredFinalize(mIdentity.forget().take());
|
DeferredFinalize(mIdentity.forget().take());
|
||||||
} else {
|
} else {
|
||||||
mIdentity = nullptr;
|
mIdentity = nullptr;
|
||||||
@ -555,6 +546,10 @@ inline void XPCWrappedNative::SetFlatJSObject(JSObject* object) {
|
|||||||
|
|
||||||
mFlatJSObject = object;
|
mFlatJSObject = object;
|
||||||
mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID);
|
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() {
|
inline void XPCWrappedNative::UnsetFlatJSObject() {
|
||||||
@ -762,10 +757,8 @@ void XPCWrappedNative::FlatJSObjectFinalized() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We also need to release any native pointers held...
|
// 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();
|
RefPtr<nsISupports> native = to->TakeNative();
|
||||||
if (native && (GetRuntime() || recordreplay::IsRecordingOrReplaying())) {
|
if (native && GetRuntime()) {
|
||||||
DeferredFinalize(native.forget().take());
|
DeferredFinalize(native.forget().take());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1033,7 +1026,6 @@ nsresult XPCWrappedNative::InitTearOff(JSContext* cx,
|
|||||||
|
|
||||||
aTearOff->SetInterface(aInterface);
|
aTearOff->SetInterface(aInterface);
|
||||||
aTearOff->SetNative(qiResult);
|
aTearOff->SetNative(qiResult);
|
||||||
RecordReplayRegisterDeferredFinalizeThing(nullptr, nullptr, qiResult);
|
|
||||||
|
|
||||||
if (needJSObject && !InitTearOffJSObject(cx, aTearOff)) {
|
if (needJSObject && !InitTearOffJSObject(cx, aTearOff)) {
|
||||||
return NS_ERROR_OUT_OF_MEMORY;
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
|
@ -28,8 +28,6 @@ XPCWrappedNativeProto::XPCWrappedNativeProto(
|
|||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
gDEBUG_LiveProtoCount++;
|
gDEBUG_LiveProtoCount++;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
RecordReplayRegisterDeferredFinalizeThing(nullptr, nullptr, mClassInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XPCWrappedNativeProto::~XPCWrappedNativeProto() {
|
XPCWrappedNativeProto::~XPCWrappedNativeProto() {
|
||||||
@ -57,6 +55,10 @@ bool XPCWrappedNativeProto::Init(JSContext* cx, nsIXPCScriptable* scriptable) {
|
|||||||
bool success = !!mJSProtoObject;
|
bool success = !!mJSProtoObject;
|
||||||
if (success) {
|
if (success) {
|
||||||
JS_SetPrivate(mJSProtoObject, this);
|
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;
|
return success;
|
||||||
|
@ -62,16 +62,8 @@ namespace recordreplay {
|
|||||||
(const PLDHashTableOps* aFirstOps, \
|
(const PLDHashTableOps* aFirstOps, \
|
||||||
const PLDHashTableOps* aSecondOps), \
|
const PLDHashTableOps* aSecondOps), \
|
||||||
(aFirstOps, aSecondOps)) \
|
(aFirstOps, aSecondOps)) \
|
||||||
Macro(SetWeakPointerJSRoot, \
|
Macro(InternalHoldJSObject, (JSObject* aJSObj), (aJSObj)) \
|
||||||
(const void* aPtr, JSObject* aJSObj), (aPtr, aJSObj)) \
|
Macro(InternalRecordReplayAssert, \
|
||||||
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, \
|
|
||||||
(const char* aFormat, va_list aArgs), \
|
(const char* aFormat, va_list aArgs), \
|
||||||
(aFormat, \
|
(aFormat, \
|
||||||
aArgs)) Macro(InternalRecordReplayAssertBytes, \
|
aArgs)) Macro(InternalRecordReplayAssertBytes, \
|
||||||
|
@ -187,54 +187,11 @@ static inline void DestroyPLDHashTableCallbacks(const PLDHashTableOps* aOps);
|
|||||||
static inline void MovePLDHashTableContents(const PLDHashTableOps* aFirstOps,
|
static inline void MovePLDHashTableContents(const PLDHashTableOps* aFirstOps,
|
||||||
const PLDHashTableOps* aSecondOps);
|
const PLDHashTableOps* aSecondOps);
|
||||||
|
|
||||||
// Associate an arbitrary pointer with a JS object root while replaying. This
|
// Prevent a JS object from ever being collected while recording or replaying.
|
||||||
// is useful for replaying the behavior of weak pointers.
|
// GC behavior is non-deterministic when recording/replaying, and preventing
|
||||||
MFBT_API void SetWeakPointerJSRoot(const void* aPtr, JSObject* aJSObj);
|
// an object from being collected ensures that finalizers which might interact
|
||||||
|
// with the recording will not execute.
|
||||||
// API for ensuring that a function executes at a consistent point when
|
static inline void HoldJSObject(JSObject* aJSObj);
|
||||||
// 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();
|
|
||||||
|
|
||||||
// Some devtools operations which execute in a replaying process can cause code
|
// 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
|
// 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))
|
(aFirstOps, aSecondOps))
|
||||||
MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(InvalidateRecording, (const char* aWhy),
|
MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(InvalidateRecording, (const char* aWhy),
|
||||||
(aWhy))
|
(aWhy))
|
||||||
MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(
|
MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(HoldJSObject, (JSObject* aObject),
|
||||||
RegisterWeakPointer,
|
(aObject))
|
||||||
(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(RecordReplayAssertBytes,
|
MOZ_MAKE_RECORD_REPLAY_WRAPPER_VOID(RecordReplayAssertBytes,
|
||||||
(const void* aData, size_t aSize),
|
(const void* aData, size_t aSize),
|
||||||
(aData, aSize))
|
(aData, aSize))
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "ProcessRecordReplay.h"
|
#include "ProcessRecordReplay.h"
|
||||||
|
|
||||||
#include "ipc/ChildInternal.h"
|
#include "ipc/ChildInternal.h"
|
||||||
|
#include "mozilla/dom/ScriptSettings.h"
|
||||||
#include "mozilla/Compression.h"
|
#include "mozilla/Compression.h"
|
||||||
#include "mozilla/Maybe.h"
|
#include "mozilla/Maybe.h"
|
||||||
#include "mozilla/Sprintf.h"
|
#include "mozilla/Sprintf.h"
|
||||||
@ -16,9 +17,7 @@
|
|||||||
#include "MemorySnapshot.h"
|
#include "MemorySnapshot.h"
|
||||||
#include "ProcessRedirect.h"
|
#include "ProcessRedirect.h"
|
||||||
#include "ProcessRewind.h"
|
#include "ProcessRewind.h"
|
||||||
#include "Trigger.h"
|
|
||||||
#include "ValueIndex.h"
|
#include "ValueIndex.h"
|
||||||
#include "WeakPointer.h"
|
|
||||||
#include "pratom.h"
|
#include "pratom.h"
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@ -149,8 +148,6 @@ MOZ_EXPORT void RecordReplayInterface_Initialize(int aArgc, char* aArgv[]) {
|
|||||||
thread->BindToCurrent();
|
thread->BindToCurrent();
|
||||||
thread->SetPassThrough(true);
|
thread->SetPassThrough(true);
|
||||||
|
|
||||||
InitializeTriggers();
|
|
||||||
InitializeWeakPointers();
|
|
||||||
InitializeMemorySnapshots();
|
InitializeMemorySnapshots();
|
||||||
Thread::SpawnAllThreads();
|
Thread::SpawnAllThreads();
|
||||||
InitializeCountdownThread();
|
InitializeCountdownThread();
|
||||||
@ -392,6 +389,14 @@ MOZ_EXPORT const char* RecordReplayInterface_InternalVirtualThingName(
|
|||||||
return name ? name : "(unknown)";
|
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"
|
} // extern "C"
|
||||||
|
|
||||||
} // namespace recordreplay
|
} // namespace recordreplay
|
||||||
|
@ -55,16 +55,7 @@ namespace recordreplay {
|
|||||||
_Macro(CallbacksFinished) \
|
_Macro(CallbacksFinished) \
|
||||||
\
|
\
|
||||||
/* Restoring a data pointer used in a callback (see Callback.h). */ \
|
/* Restoring a data pointer used in a callback (see Callback.h). */ \
|
||||||
_Macro(RestoreCallbackData) \
|
_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)
|
|
||||||
|
|
||||||
// ID of an event in a thread's event stream. Each ID in the stream is followed
|
// ID of an event in a thread's event stream. Each ID in the stream is followed
|
||||||
// by data associated with the event.
|
// 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 "JSControl.h"
|
||||||
|
|
||||||
|
#include "mozilla/Base64.h"
|
||||||
#include "mozilla/ClearOnShutdown.h"
|
#include "mozilla/ClearOnShutdown.h"
|
||||||
#include "mozilla/StaticPtr.h"
|
#include "mozilla/StaticPtr.h"
|
||||||
#include "js/CharacterEncoding.h"
|
#include "js/CharacterEncoding.h"
|
||||||
@ -13,6 +14,7 @@
|
|||||||
#include "js/JSON.h"
|
#include "js/JSON.h"
|
||||||
#include "js/PropertySpec.h"
|
#include "js/PropertySpec.h"
|
||||||
#include "ChildInternal.h"
|
#include "ChildInternal.h"
|
||||||
|
#include "MemorySnapshot.h"
|
||||||
#include "ParentInternal.h"
|
#include "ParentInternal.h"
|
||||||
#include "nsImportModule.h"
|
#include "nsImportModule.h"
|
||||||
#include "rrIControl.h"
|
#include "rrIControl.h"
|
||||||
|
@ -31,9 +31,7 @@ if CONFIG['OS_ARCH'] == 'Darwin' and CONFIG['NIGHTLY_BUILD']:
|
|||||||
'ProcessRewind.cpp',
|
'ProcessRewind.cpp',
|
||||||
'Thread.cpp',
|
'Thread.cpp',
|
||||||
'ThreadSnapshot.cpp',
|
'ThreadSnapshot.cpp',
|
||||||
'Trigger.cpp',
|
|
||||||
'ValueIndex.cpp',
|
'ValueIndex.cpp',
|
||||||
'WeakPointer.cpp',
|
|
||||||
]
|
]
|
||||||
SOURCES += [
|
SOURCES += [
|
||||||
# ProcessRedirect includes udis86 directly and will not compile if the
|
# 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) {
|
if (mDeferredFinalizerTable.Count() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -12,48 +12,12 @@
|
|||||||
void mozilla::DeferredFinalize(nsISupports* aSupports) {
|
void mozilla::DeferredFinalize(nsISupports* aSupports) {
|
||||||
CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get();
|
CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get();
|
||||||
MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now");
|
MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now");
|
||||||
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
|
rt->DeferredFinalize(aSupports);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void mozilla::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
|
void mozilla::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
|
||||||
DeferredFinalizeFunction aFunc, void* aThing) {
|
DeferredFinalizeFunction aFunc, void* aThing) {
|
||||||
CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get();
|
CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get();
|
||||||
MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now");
|
MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now");
|
||||||
if (mozilla::recordreplay::IsRecordingOrReplaying()) {
|
rt->DeferredFinalize(aAppendFunc, aFunc, aThing);
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -27,20 +27,6 @@ void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
|
|||||||
|
|
||||||
void DeferredFinalize(nsISupports* aSupports);
|
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
|
} // namespace mozilla
|
||||||
|
|
||||||
#endif // mozilla_DeferredFinalize_h
|
#endif // mozilla_DeferredFinalize_h
|
||||||
|
Loading…
x
Reference in New Issue
Block a user