| /* -*- 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 "gc/Marking.h" |
| |
| #include "jsgcinlines.h" |
| #include "jsobjinlines.h" |
| |
| using namespace js; |
| using namespace js::gc; |
| |
| inline HashNumber |
| DefaultHasher<WatchKey>::hash(const Lookup &key) |
| { |
| return DefaultHasher<JSObject *>::hash(key.object.get()) ^ HashId(key.id.get()); |
| } |
| |
| class AutoEntryHolder { |
| typedef WatchpointMap::Map Map; |
| Map ↦ |
| Map::Ptr p; |
| uint32_t gen; |
| RootedObject obj; |
| RootedId id; |
| |
| public: |
| AutoEntryHolder(JSContext *cx, Map &map, Map::Ptr p) |
| : map(map), p(p), gen(map.generation()), obj(cx, p->key.object), id(cx, p->key.id) { |
| JS_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; |
| } |
| }; |
| |
| bool |
| WatchpointMap::init() |
| { |
| return map.init(); |
| } |
| |
| #ifdef JSGC_GENERATIONAL |
| void |
| Mark(JSTracer *trc, WatchKey *key, const char *name) |
| { |
| MarkId(trc, &key->id, "WatchKey id"); |
| MarkObject(trc, &key->object, "WatchKey id"); |
| } |
| #endif |
| |
| static void |
| WatchpointWriteBarrierPost(JSRuntime *rt, WatchpointMap::Map *map, const WatchKey &key, |
| const Watchpoint &val) |
| { |
| #ifdef JSGC_GENERATIONAL |
| if ((JSID_IS_OBJECT(key.id) && IsInsideNursery(rt, JSID_TO_OBJECT(key.id))) || |
| (JSID_IS_STRING(key.id) && IsInsideNursery(rt, JSID_TO_STRING(key.id))) || |
| IsInsideNursery(rt, key.object) || |
| IsInsideNursery(rt, val.closure)) |
| { |
| typedef HashKeyRef<WatchpointMap::Map, WatchKey> WatchKeyRef; |
| rt->gcStoreBuffer.putGeneric(WatchKeyRef(map, key)); |
| } |
| #endif |
| } |
| |
| bool |
| WatchpointMap::watch(JSContext *cx, HandleObject obj, HandleId id, |
| JSWatchPointHandler handler, HandleObject closure) |
| { |
| JS_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id)); |
| |
| if (!obj->setWatched(cx)) |
| return false; |
| |
| Watchpoint w; |
| w.handler = handler; |
| w.closure = closure; |
| w.held = false; |
| if (!map.put(WatchKey(obj, id), w)) { |
| js_ReportOutOfMemory(cx); |
| return false; |
| } |
| WatchpointWriteBarrierPost(cx->runtime(), &map, WatchKey(obj, id), w); |
| 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::ExposeGCThingToActiveJS(p->value.closure, JSTRACE_OBJECT); |
| *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()) { |
| if (Shape *shape = obj->nativeLookup(cx, id)) { |
| if (shape->hasSlot()) |
| old = obj->nativeGetSlot(shape->slot()); |
| } |
| } |
| |
| // Read barrier to prevent an incorrectly gray closure from escaping the |
| // watchpoint. See the comment before UnmarkGrayChildren in gc/Marking.cpp |
| JS::ExposeGCThingToActiveJS(closure, JSTRACE_OBJECT); |
| |
| /* Call the handler. */ |
| return handler(cx, obj, id, old, vp.address(), closure); |
| } |
| |
| bool |
| WatchpointMap::markCompartmentIteratively(JSCompartment *c, JSTracer *trc) |
| { |
| if (!c->watchpointMap) |
| return false; |
| return c->watchpointMap->markIteratively(trc); |
| } |
| |
| 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 = IsObjectMarked(const_cast<EncapsulatedPtrObject *>(&entry.key.object)); |
| if (objectIsLive || entry.value.held) { |
| if (!objectIsLive) { |
| MarkObject(trc, const_cast<EncapsulatedPtrObject *>(&entry.key.object), |
| "held Watchpoint object"); |
| marked = true; |
| } |
| |
| JS_ASSERT(JSID_IS_STRING(priorKeyId) || JSID_IS_INT(priorKeyId)); |
| MarkId(trc, const_cast<EncapsulatedId *>(&entry.key.id), "WatchKey::id"); |
| |
| if (entry.value.closure && !IsObjectMarked(&entry.value.closure)) { |
| MarkObject(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(); |
| JSObject *priorKeyObj = entry.key.object; |
| jsid priorKeyId = entry.key.id; |
| JS_ASSERT(JSID_IS_STRING(priorKeyId) || JSID_IS_INT(priorKeyId)); |
| |
| MarkObject(trc, const_cast<EncapsulatedPtrObject *>(&entry.key.object), |
| "held Watchpoint object"); |
| MarkId(trc, const_cast<EncapsulatedId *>(&entry.key.id), "WatchKey::id"); |
| MarkObject(trc, &entry.value.closure, "Watchpoint::closure"); |
| |
| if (priorKeyObj != entry.key.object || priorKeyId != entry.key.id) |
| e.rekeyFront(entry.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(); |
| RelocatablePtrObject obj(entry.key.object); |
| if (IsObjectAboutToBeFinalized(&obj)) { |
| JS_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->runtime; |
| for (CompartmentsIter comp(rt); !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->callback(trc, NULL, |
| entry.key.object.get(), JSTRACE_OBJECT, |
| entry.value.closure.get(), JSTRACE_OBJECT); |
| } |
| } |