If a user does removeFront or rekeyFront on an Enum, then continues to use it, it will fail, potentially randomly. This patch makes the failure explicit in debug builds.
238 lines
9.0 KiB
C++
238 lines
9.0 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* vim: set ts=4 sw=4 et tw=99:
|
|
*
|
|
* 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 jsweakmap_h___
|
|
#define jsweakmap_h___
|
|
|
|
#include "jsapi.h"
|
|
#include "jsfriendapi.h"
|
|
#include "jscntxt.h"
|
|
#include "jsobj.h"
|
|
|
|
#include "gc/Marking.h"
|
|
#include "js/HashTable.h"
|
|
|
|
namespace js {
|
|
|
|
// A subclass template of js::HashMap whose keys and values may be garbage-collected. When
|
|
// a key is collected, the table entry disappears, dropping its reference to the value.
|
|
//
|
|
// More precisely:
|
|
//
|
|
// A WeakMap entry is collected if and only if either the WeakMap or the entry's key
|
|
// is collected. If an entry is not collected, it remains in the WeakMap and it has a
|
|
// strong reference to the value.
|
|
//
|
|
// You must call this table's 'mark' method when the object of which it is a part is
|
|
// reached by the garbage collection tracer. Once a table is known to be live, the
|
|
// implementation takes care of the iterative marking needed for weak tables and removing
|
|
// table entries when collection is complete.
|
|
//
|
|
// You may provide your own MarkPolicy class to specify how keys and values are marked; a
|
|
// policy template provides default definitions for some common key/value type
|
|
// combinations.
|
|
//
|
|
// Details:
|
|
//
|
|
// The interface is as for a js::HashMap, with the following additions:
|
|
//
|
|
// - You must call the WeakMap's 'trace' member function when you discover that the map is
|
|
// part of a live object. (You'll typically call this from the containing type's 'trace'
|
|
// function.)
|
|
//
|
|
// - There is no AllocPolicy parameter; these are used with our garbage collector, so
|
|
// RuntimeAllocPolicy is hard-wired.
|
|
//
|
|
// - Optional fourth and fifth parameters are the MarkPolicies for the key and value type.
|
|
// A MarkPolicy has the constructor:
|
|
//
|
|
// MarkPolicy(JSTracer *)
|
|
//
|
|
// and the following member functions:
|
|
//
|
|
// bool isMarked(const Type &x)
|
|
// Return true if x has been marked as live by the garbage collector.
|
|
//
|
|
// bool mark(Type &x)
|
|
// Return false if x is already marked. Otherwise, mark x and return true.
|
|
//
|
|
// If omitted, the MarkPolicy parameter defaults to js::DefaultMarkPolicy<Type>,
|
|
// a policy template with the obvious definitions for some typical
|
|
// SpiderMonkey type combinations.
|
|
|
|
// The value for the next pointer for maps not in the map list.
|
|
static WeakMapBase * const WeakMapNotInList = reinterpret_cast<WeakMapBase *>(1);
|
|
|
|
typedef Vector<WeakMapBase *, 0, SystemAllocPolicy> WeakMapVector;
|
|
|
|
// Common base class for all WeakMap specializations. The collector uses this to call
|
|
// their markIteratively and sweep methods.
|
|
class WeakMapBase {
|
|
public:
|
|
WeakMapBase(JSObject *memOf) : memberOf(memOf), next(WeakMapNotInList) { }
|
|
virtual ~WeakMapBase() { }
|
|
|
|
void trace(JSTracer *tracer) {
|
|
if (IS_GC_MARKING_TRACER(tracer)) {
|
|
// We don't do anything with a WeakMap at trace time. Rather, we wait until as
|
|
// many keys as possible have been marked, and add ourselves to the list of
|
|
// known-live WeakMaps to be scanned in the iterative marking phase, by
|
|
// markAllIteratively.
|
|
JS_ASSERT(!tracer->eagerlyTraceWeakMaps);
|
|
|
|
// Add ourselves to the list if we are not already in the list. We can already
|
|
// be in the list if the weak map is marked more than once due delayed marking.
|
|
if (next == WeakMapNotInList) {
|
|
JSRuntime *rt = tracer->runtime;
|
|
next = rt->gcWeakMapList;
|
|
rt->gcWeakMapList = this;
|
|
}
|
|
} else {
|
|
// If we're not actually doing garbage collection, the keys won't be marked
|
|
// nicely as needed by the true ephemeral marking algorithm --- custom tracers
|
|
// such as the cycle collector must use their own means for cycle detection.
|
|
// So here we do a conservative approximation: pretend all keys are live.
|
|
if (tracer->eagerlyTraceWeakMaps)
|
|
nonMarkingTrace(tracer);
|
|
}
|
|
}
|
|
|
|
// Garbage collector entry points.
|
|
|
|
// Check all weak maps that have been marked as live so far in this garbage
|
|
// collection, and mark the values of all entries that have become strong references
|
|
// to them. Return true if we marked any new values, indicating that we need to make
|
|
// another pass. In other words, mark my marked maps' marked members' mid-collection.
|
|
static bool markAllIteratively(JSTracer *tracer);
|
|
|
|
// Remove entries whose keys are dead from all weak maps marked as live in this
|
|
// garbage collection.
|
|
static void sweepAll(JSTracer *tracer);
|
|
|
|
// Trace all delayed weak map bindings. Used by the cycle collector.
|
|
static void traceAllMappings(WeakMapTracer *tracer);
|
|
|
|
void check() { JS_ASSERT(next == WeakMapNotInList); }
|
|
|
|
// Remove everything from the live weak map list.
|
|
static void resetWeakMapList(JSRuntime *rt);
|
|
|
|
// Save and restore the live weak map list to a vector.
|
|
static bool saveWeakMapList(JSRuntime *rt, WeakMapVector &vector);
|
|
static void restoreWeakMapList(JSRuntime *rt, WeakMapVector &vector);
|
|
|
|
protected:
|
|
// Instance member functions called by the above. Instantiations of WeakMap override
|
|
// these with definitions appropriate for their Key and Value types.
|
|
virtual void nonMarkingTrace(JSTracer *tracer) = 0;
|
|
virtual bool markIteratively(JSTracer *tracer) = 0;
|
|
virtual void sweep(JSTracer *tracer) = 0;
|
|
virtual void traceMappings(WeakMapTracer *tracer) = 0;
|
|
|
|
// Object that this weak map is part of, if any.
|
|
JSObject *memberOf;
|
|
|
|
private:
|
|
// Link in a list of WeakMaps to mark iteratively and sweep in this garbage
|
|
// collection, headed by JSRuntime::gcWeakMapList. The last element of the list
|
|
// has NULL as its next. Maps not in the list have WeakMapNotInList as their
|
|
// next. We must distinguish these cases to avoid creating infinite lists
|
|
// when a weak map gets traced twice due to delayed marking.
|
|
WeakMapBase *next;
|
|
};
|
|
|
|
template <class Key, class Value,
|
|
class HashPolicy = DefaultHasher<Key> >
|
|
class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>, public WeakMapBase {
|
|
private:
|
|
typedef HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy> Base;
|
|
typedef typename Base::Enum Enum;
|
|
|
|
public:
|
|
typedef typename Base::Range Range;
|
|
|
|
explicit WeakMap(JSRuntime *rt, JSObject *memOf=NULL) : Base(rt), WeakMapBase(memOf) { }
|
|
explicit WeakMap(JSContext *cx, JSObject *memOf=NULL) : Base(cx), WeakMapBase(memOf) { }
|
|
|
|
/* Use with caution, as result can be affected by garbage collection. */
|
|
Range nondeterministicAll() {
|
|
return Base::all();
|
|
}
|
|
|
|
private:
|
|
bool markValue(JSTracer *trc, Value *x) {
|
|
if (gc::IsMarked(x))
|
|
return false;
|
|
gc::Mark(trc, x, "WeakMap entry");
|
|
return true;
|
|
}
|
|
|
|
void nonMarkingTrace(JSTracer *trc) {
|
|
for (Range r = Base::all(); !r.empty(); r.popFront())
|
|
markValue(trc, &r.front().value);
|
|
}
|
|
|
|
bool markIteratively(JSTracer *trc) {
|
|
bool markedAny = false;
|
|
for (Enum e(*this); !e.empty(); e.popFront()) {
|
|
/* If the entry is live, ensure its key and value are marked. */
|
|
Key k(e.front().key);
|
|
bool keyIsMarked = gc::IsMarked(&k);
|
|
if (keyIsMarked) {
|
|
if (markValue(trc, &e.front().value))
|
|
markedAny = true;
|
|
e.rekeyFront(k);
|
|
}
|
|
}
|
|
return markedAny;
|
|
}
|
|
|
|
void sweep(JSTracer *trc) {
|
|
/* Remove all entries whose keys remain unmarked. */
|
|
for (Enum e(*this); !e.empty(); e.popFront()) {
|
|
Key k(e.front().key);
|
|
if (!gc::IsMarked(&k))
|
|
e.removeFront();
|
|
}
|
|
|
|
#if DEBUG
|
|
/*
|
|
* Once we've swept, all remaining edges should stay within the
|
|
* known-live part of the graph.
|
|
*/
|
|
for (Range r = Base::all(); !r.empty(); r.popFront()) {
|
|
Key k(r.front().key);
|
|
Value v(r.front().value);
|
|
JS_ASSERT(gc::IsMarked(&k));
|
|
JS_ASSERT(gc::IsMarked(&v));
|
|
JS_ASSERT(k == r.front().key);
|
|
JS_ASSERT(v == r.front().value);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* memberOf can be NULL, which means that the map is not part of a JSObject. */
|
|
void traceMappings(WeakMapTracer *tracer) {
|
|
for (Range r = Base::all(); !r.empty(); r.popFront()) {
|
|
gc::Cell *key = gc::ToMarkable(r.front().key);
|
|
gc::Cell *value = gc::ToMarkable(r.front().value);
|
|
if (key && value) {
|
|
tracer->callback(tracer, memberOf,
|
|
key, gc::TraceKind(r.front().key),
|
|
value, gc::TraceKind(r.front().value));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
} /* namespace js */
|
|
|
|
extern JSObject *
|
|
js_InitWeakMapClass(JSContext *cx, JSObject *obj);
|
|
|
|
#endif
|