diff options
Diffstat (limited to 'js/src/jswatchpoint.cpp')
-rw-r--r-- | js/src/jswatchpoint.cpp | 245 |
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())); + } +} |