js/src/jswatchpoint.cpp
author Eugen Sawin <esawin@mozilla.com>
Mon, 18 Sep 2017 22:09:37 +0200
changeset 383197 505c1e22424aa59809ccf33cec9bf1d20d784c2a
parent 379331 8d1a2ad4d81160488a7ec54cb0ed3abfa369ba77
child 384023 641d9a23bb4ec76b4dbc4f8600566a29c1e8b28c
permissions -rw-r--r--
Bug 1401002 - [1.1] Don't override chrome window type with default argument value. r=tromey

/* -*- 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 (!JSObject::setWatched(cx, obj))
        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)
{
    if (Map::Ptr p = map.lookup(WatchKey(obj, id)))
        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(GCMarker* marker)
{
    bool marked = false;
    for (Map::Enum e(map); !e.empty(); e.popFront()) {
        Map::Entry& entry = e.front();
        auto& object = entry.mutableKey().object;
        bool objectIsLive = IsMarked(marker->runtime(), &object);
        if (objectIsLive || entry.value().held) {
            if (!objectIsLive) {
                TraceEdge(marker, &object, "held Watchpoint object");
                marked = true;
            }

            auto& id = entry.mutableKey().id;
            MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id) || JSID_IS_SYMBOL(id));
            TraceEdge(marker, &id, "WatchKey::id");

            auto& closure = entry.value().closure;
            if (closure && !IsMarked(marker->runtime(), &closure)) {
                TraceEdge(marker, &closure, "Watchpoint::closure");
                marked = true;
            }
        }
    }
    return marked;
}

void
WatchpointMap::trace(JSTracer* trc)
{
    for (Map::Enum e(map); !e.empty(); e.popFront()) {
        Map::Entry& entry = e.front();
        auto& id = entry.mutableKey().id;
        MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id) || JSID_IS_SYMBOL(id));
        TraceEdge(trc, &entry.mutableKey().object, "held Watchpoint object");
        TraceEdge(trc, &id, "WatchKey::id");
        TraceEdge(trc, &entry.value().closure, "Watchpoint::closure");
    }
}

/* static */ void
WatchpointMap::sweepAll(JSRuntime* rt)
{
    // This is called during compacting GC. Watchpoint closure pointers can be
    // cross-compartment so we have to sweep all watchpoint maps, not just those
    // owned by compartments we are compacting.
    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();
        if (IsAboutToBeFinalized(&entry.mutableKey().object)) {
            MOZ_ASSERT(!entry.value().held);
            e.removeFront();
        }
    }
}

void
WatchpointMap::traceAll(WeakMapTracer* trc)
{
    JSRuntime* rt = trc->runtime;
    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()));
    }
}