summaryrefslogtreecommitdiffstats
path: root/js/src/jswatchpoint.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jswatchpoint.cpp')
-rw-r--r--js/src/jswatchpoint.cpp245
1 files changed, 245 insertions, 0 deletions
diff --git a/js/src/jswatchpoint.cpp b/js/src/jswatchpoint.cpp
new file mode 100644
index 000000000..3cf43e219
--- /dev/null
+++ b/js/src/jswatchpoint.cpp
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 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/. */
+
+#include "jswatchpoint.h"
+
+#include "jsatom.h"
+#include "jscompartment.h"
+#include "jsfriendapi.h"
+
+#include "gc/Marking.h"
+#include "vm/Shape.h"
+
+#include "jsgcinlines.h"
+
+using namespace js;
+using namespace js::gc;
+
+inline HashNumber
+WatchKeyHasher::hash(const Lookup& key)
+{
+ return MovableCellHasher<PreBarrieredObject>::hash(key.object) ^ HashId(key.id);
+}
+
+namespace {
+
+class AutoEntryHolder {
+ typedef WatchpointMap::Map Map;
+ Generation gen;
+ Map& map;
+ Map::Ptr p;
+ RootedObject obj;
+ RootedId id;
+
+ public:
+ AutoEntryHolder(JSContext* cx, Map& map, Map::Ptr p)
+ : gen(map.generation()), map(map), p(p), obj(cx, p->key().object), id(cx, p->key().id)
+ {
+ MOZ_ASSERT(!p->value().held);
+ p->value().held = true;
+ }
+
+ ~AutoEntryHolder() {
+ if (gen != map.generation())
+ p = map.lookup(WatchKey(obj, id));
+ if (p)
+ p->value().held = false;
+ }
+};
+
+} /* anonymous namespace */
+
+bool
+WatchpointMap::init()
+{
+ return map.init();
+}
+
+bool
+WatchpointMap::watch(JSContext* cx, HandleObject obj, HandleId id,
+ JSWatchPointHandler handler, HandleObject closure)
+{
+ MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id) || JSID_IS_SYMBOL(id));
+
+ if (!obj->setWatched(cx))
+ return false;
+
+ Watchpoint w(handler, closure, false);
+ if (!map.put(WatchKey(obj, id), w)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ /*
+ * For generational GC, we don't need to post-barrier writes to the
+ * hashtable here because we mark all watchpoints as part of root marking in
+ * markAll().
+ */
+ return true;
+}
+
+void
+WatchpointMap::unwatch(JSObject* obj, jsid id,
+ JSWatchPointHandler* handlerp, JSObject** closurep)
+{
+ if (Map::Ptr p = map.lookup(WatchKey(obj, id))) {
+ if (handlerp)
+ *handlerp = p->value().handler;
+ if (closurep) {
+ // Read barrier to prevent an incorrectly gray closure from escaping the
+ // watchpoint. See the comment before UnmarkGrayChildren in gc/Marking.cpp
+ JS::ExposeObjectToActiveJS(p->value().closure);
+ *closurep = p->value().closure;
+ }
+ map.remove(p);
+ }
+}
+
+void
+WatchpointMap::unwatchObject(JSObject* obj)
+{
+ for (Map::Enum e(map); !e.empty(); e.popFront()) {
+ Map::Entry& entry = e.front();
+ if (entry.key().object == obj)
+ e.removeFront();
+ }
+}
+
+void
+WatchpointMap::clear()
+{
+ map.clear();
+}
+
+bool
+WatchpointMap::triggerWatchpoint(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)
+{
+ Map::Ptr p = map.lookup(WatchKey(obj, id));
+ if (!p || p->value().held)
+ return true;
+
+ AutoEntryHolder holder(cx, map, p);
+
+ /* Copy the entry, since GC would invalidate p. */
+ JSWatchPointHandler handler = p->value().handler;
+ RootedObject closure(cx, p->value().closure);
+
+ /* Determine the property's old value. */
+ Value old;
+ old.setUndefined();
+ if (obj->isNative()) {
+ NativeObject* nobj = &obj->as<NativeObject>();
+ if (Shape* shape = nobj->lookup(cx, id)) {
+ if (shape->hasSlot())
+ old = nobj->getSlot(shape->slot());
+ }
+ }
+
+ // Read barrier to prevent an incorrectly gray closure from escaping the
+ // watchpoint. See the comment before UnmarkGrayChildren in gc/Marking.cpp
+ JS::ExposeObjectToActiveJS(closure);
+
+ /* Call the handler. */
+ return handler(cx, obj, id, old, vp.address(), closure);
+}
+
+bool
+WatchpointMap::markIteratively(JSTracer* trc)
+{
+ bool marked = false;
+ for (Map::Enum e(map); !e.empty(); e.popFront()) {
+ Map::Entry& entry = e.front();
+ JSObject* priorKeyObj = entry.key().object;
+ jsid priorKeyId(entry.key().id.get());
+ bool objectIsLive =
+ IsMarked(trc->runtime(), const_cast<PreBarrieredObject*>(&entry.key().object));
+ if (objectIsLive || entry.value().held) {
+ if (!objectIsLive) {
+ TraceEdge(trc, const_cast<PreBarrieredObject*>(&entry.key().object),
+ "held Watchpoint object");
+ marked = true;
+ }
+
+ MOZ_ASSERT(JSID_IS_STRING(priorKeyId) ||
+ JSID_IS_INT(priorKeyId) ||
+ JSID_IS_SYMBOL(priorKeyId));
+ TraceEdge(trc, const_cast<PreBarrieredId*>(&entry.key().id), "WatchKey::id");
+
+ if (entry.value().closure && !IsMarked(trc->runtime(), &entry.value().closure)) {
+ TraceEdge(trc, &entry.value().closure, "Watchpoint::closure");
+ marked = true;
+ }
+
+ /* We will sweep this entry in sweepAll if !objectIsLive. */
+ if (priorKeyObj != entry.key().object || priorKeyId != entry.key().id)
+ e.rekeyFront(WatchKey(entry.key().object, entry.key().id));
+ }
+ }
+ return marked;
+}
+
+void
+WatchpointMap::markAll(JSTracer* trc)
+{
+ for (Map::Enum e(map); !e.empty(); e.popFront()) {
+ Map::Entry& entry = e.front();
+ WatchKey key = entry.key();
+ WatchKey prior = key;
+ MOZ_ASSERT(JSID_IS_STRING(prior.id) || JSID_IS_INT(prior.id) || JSID_IS_SYMBOL(prior.id));
+
+ TraceEdge(trc, const_cast<PreBarrieredObject*>(&key.object),
+ "held Watchpoint object");
+ TraceEdge(trc, const_cast<PreBarrieredId*>(&key.id), "WatchKey::id");
+ TraceEdge(trc, &entry.value().closure, "Watchpoint::closure");
+
+ if (prior.object != key.object || prior.id != key.id)
+ e.rekeyFront(key);
+ }
+}
+
+void
+WatchpointMap::sweepAll(JSRuntime* rt)
+{
+ for (GCCompartmentsIter c(rt); !c.done(); c.next()) {
+ if (WatchpointMap* wpmap = c->watchpointMap)
+ wpmap->sweep();
+ }
+}
+
+void
+WatchpointMap::sweep()
+{
+ for (Map::Enum e(map); !e.empty(); e.popFront()) {
+ Map::Entry& entry = e.front();
+ JSObject* obj(entry.key().object);
+ if (IsAboutToBeFinalizedUnbarriered(&obj)) {
+ MOZ_ASSERT(!entry.value().held);
+ e.removeFront();
+ } else if (obj != entry.key().object) {
+ e.rekeyFront(WatchKey(obj, entry.key().id));
+ }
+ }
+}
+
+void
+WatchpointMap::traceAll(WeakMapTracer* trc)
+{
+ JSRuntime* rt = trc->context;
+ for (CompartmentsIter comp(rt, SkipAtoms); !comp.done(); comp.next()) {
+ if (WatchpointMap* wpmap = comp->watchpointMap)
+ wpmap->trace(trc);
+ }
+}
+
+void
+WatchpointMap::trace(WeakMapTracer* trc)
+{
+ for (Map::Range r = map.all(); !r.empty(); r.popFront()) {
+ Map::Entry& entry = r.front();
+ trc->trace(nullptr,
+ JS::GCCellPtr(entry.key().object.get()),
+ JS::GCCellPtr(entry.value().closure.get()));
+ }
+}