blob: db9d8a198d3588765e9c2f1996b8ef0dc0100e81 [file] [log] [blame]
/* -*- 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;
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);
}
}