michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "jswatchpoint.h" michael@0: michael@0: #include "jsatom.h" michael@0: #include "jscompartment.h" michael@0: #include "jsfriendapi.h" michael@0: michael@0: #include "gc/Marking.h" michael@0: michael@0: #include "jsgcinlines.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: michael@0: inline HashNumber michael@0: DefaultHasher::hash(const Lookup &key) michael@0: { michael@0: return DefaultHasher::hash(key.object.get()) ^ HashId(key.id.get()); michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class AutoEntryHolder { michael@0: typedef WatchpointMap::Map Map; michael@0: Map ↦ michael@0: Map::Ptr p; michael@0: uint32_t gen; michael@0: RootedObject obj; michael@0: RootedId id; michael@0: michael@0: public: michael@0: AutoEntryHolder(JSContext *cx, Map &map, Map::Ptr p) michael@0: : map(map), p(p), gen(map.generation()), obj(cx, p->key().object), id(cx, p->key().id) michael@0: { michael@0: JS_ASSERT(!p->value().held); michael@0: p->value().held = true; michael@0: } michael@0: michael@0: ~AutoEntryHolder() { michael@0: if (gen != map.generation()) michael@0: p = map.lookup(WatchKey(obj, id)); michael@0: if (p) michael@0: p->value().held = false; michael@0: } michael@0: }; michael@0: michael@0: } /* anonymous namespace */ michael@0: michael@0: bool michael@0: WatchpointMap::init() michael@0: { michael@0: return map.init(); michael@0: } michael@0: michael@0: bool michael@0: WatchpointMap::watch(JSContext *cx, HandleObject obj, HandleId id, michael@0: JSWatchPointHandler handler, HandleObject closure) michael@0: { michael@0: JS_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id)); michael@0: michael@0: if (!obj->setWatched(cx)) michael@0: return false; michael@0: michael@0: Watchpoint w(handler, closure, false); michael@0: if (!map.put(WatchKey(obj, id), w)) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: /* michael@0: * For generational GC, we don't need to post-barrier writes to the michael@0: * hashtable here because we mark all watchpoints as part of root marking in michael@0: * markAll(). michael@0: */ michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: WatchpointMap::unwatch(JSObject *obj, jsid id, michael@0: JSWatchPointHandler *handlerp, JSObject **closurep) michael@0: { michael@0: if (Map::Ptr p = map.lookup(WatchKey(obj, id))) { michael@0: if (handlerp) michael@0: *handlerp = p->value().handler; michael@0: if (closurep) { michael@0: // Read barrier to prevent an incorrectly gray closure from escaping the michael@0: // watchpoint. See the comment before UnmarkGrayChildren in gc/Marking.cpp michael@0: JS::ExposeGCThingToActiveJS(p->value().closure, JSTRACE_OBJECT); michael@0: *closurep = p->value().closure; michael@0: } michael@0: map.remove(p); michael@0: } michael@0: } michael@0: michael@0: void michael@0: WatchpointMap::unwatchObject(JSObject *obj) michael@0: { michael@0: for (Map::Enum e(map); !e.empty(); e.popFront()) { michael@0: Map::Entry &entry = e.front(); michael@0: if (entry.key().object == obj) michael@0: e.removeFront(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: WatchpointMap::clear() michael@0: { michael@0: map.clear(); michael@0: } michael@0: michael@0: bool michael@0: WatchpointMap::triggerWatchpoint(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp) michael@0: { michael@0: Map::Ptr p = map.lookup(WatchKey(obj, id)); michael@0: if (!p || p->value().held) michael@0: return true; michael@0: michael@0: AutoEntryHolder holder(cx, map, p); michael@0: michael@0: /* Copy the entry, since GC would invalidate p. */ michael@0: JSWatchPointHandler handler = p->value().handler; michael@0: RootedObject closure(cx, p->value().closure); michael@0: michael@0: /* Determine the property's old value. */ michael@0: Value old; michael@0: old.setUndefined(); michael@0: if (obj->isNative()) { michael@0: if (Shape *shape = obj->nativeLookup(cx, id)) { michael@0: if (shape->hasSlot()) michael@0: old = obj->nativeGetSlot(shape->slot()); michael@0: } michael@0: } michael@0: michael@0: // Read barrier to prevent an incorrectly gray closure from escaping the michael@0: // watchpoint. See the comment before UnmarkGrayChildren in gc/Marking.cpp michael@0: JS::ExposeGCThingToActiveJS(closure, JSTRACE_OBJECT); michael@0: michael@0: /* Call the handler. */ michael@0: return handler(cx, obj, id, old, vp.address(), closure); michael@0: } michael@0: michael@0: bool michael@0: WatchpointMap::markCompartmentIteratively(JSCompartment *c, JSTracer *trc) michael@0: { michael@0: if (!c->watchpointMap) michael@0: return false; michael@0: return c->watchpointMap->markIteratively(trc); michael@0: } michael@0: michael@0: bool michael@0: WatchpointMap::markIteratively(JSTracer *trc) michael@0: { michael@0: bool marked = false; michael@0: for (Map::Enum e(map); !e.empty(); e.popFront()) { michael@0: Map::Entry &entry = e.front(); michael@0: JSObject *priorKeyObj = entry.key().object; michael@0: jsid priorKeyId(entry.key().id.get()); michael@0: bool objectIsLive = michael@0: IsObjectMarked(const_cast(&entry.key().object)); michael@0: if (objectIsLive || entry.value().held) { michael@0: if (!objectIsLive) { michael@0: MarkObject(trc, const_cast(&entry.key().object), michael@0: "held Watchpoint object"); michael@0: marked = true; michael@0: } michael@0: michael@0: JS_ASSERT(JSID_IS_STRING(priorKeyId) || JSID_IS_INT(priorKeyId)); michael@0: MarkId(trc, const_cast(&entry.key().id), "WatchKey::id"); michael@0: michael@0: if (entry.value().closure && !IsObjectMarked(&entry.value().closure)) { michael@0: MarkObject(trc, &entry.value().closure, "Watchpoint::closure"); michael@0: marked = true; michael@0: } michael@0: michael@0: /* We will sweep this entry in sweepAll if !objectIsLive. */ michael@0: if (priorKeyObj != entry.key().object || priorKeyId != entry.key().id) michael@0: e.rekeyFront(WatchKey(entry.key().object, entry.key().id)); michael@0: } michael@0: } michael@0: return marked; michael@0: } michael@0: michael@0: void michael@0: WatchpointMap::markAll(JSTracer *trc) michael@0: { michael@0: for (Map::Enum e(map); !e.empty(); e.popFront()) { michael@0: Map::Entry &entry = e.front(); michael@0: WatchKey key = entry.key(); michael@0: WatchKey prior = key; michael@0: JS_ASSERT(JSID_IS_STRING(prior.id) || JSID_IS_INT(prior.id)); michael@0: michael@0: MarkObject(trc, const_cast(&key.object), michael@0: "held Watchpoint object"); michael@0: MarkId(trc, const_cast(&key.id), "WatchKey::id"); michael@0: MarkObject(trc, &entry.value().closure, "Watchpoint::closure"); michael@0: michael@0: if (prior.object != key.object || prior.id != key.id) michael@0: e.rekeyFront(key); michael@0: } michael@0: } michael@0: michael@0: void michael@0: WatchpointMap::sweepAll(JSRuntime *rt) michael@0: { michael@0: for (GCCompartmentsIter c(rt); !c.done(); c.next()) { michael@0: if (WatchpointMap *wpmap = c->watchpointMap) michael@0: wpmap->sweep(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: WatchpointMap::sweep() michael@0: { michael@0: for (Map::Enum e(map); !e.empty(); e.popFront()) { michael@0: Map::Entry &entry = e.front(); michael@0: JSObject *obj(entry.key().object); michael@0: if (IsObjectAboutToBeFinalized(&obj)) { michael@0: JS_ASSERT(!entry.value().held); michael@0: e.removeFront(); michael@0: } else if (obj != entry.key().object) { michael@0: e.rekeyFront(WatchKey(obj, entry.key().id)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: WatchpointMap::traceAll(WeakMapTracer *trc) michael@0: { michael@0: JSRuntime *rt = trc->runtime; michael@0: for (CompartmentsIter comp(rt, SkipAtoms); !comp.done(); comp.next()) { michael@0: if (WatchpointMap *wpmap = comp->watchpointMap) michael@0: wpmap->trace(trc); michael@0: } michael@0: } michael@0: michael@0: void michael@0: WatchpointMap::trace(WeakMapTracer *trc) michael@0: { michael@0: for (Map::Range r = map.all(); !r.empty(); r.popFront()) { michael@0: Map::Entry &entry = r.front(); michael@0: trc->callback(trc, nullptr, michael@0: entry.key().object.get(), JSTRACE_OBJECT, michael@0: entry.value().closure.get(), JSTRACE_OBJECT); michael@0: } michael@0: }