Browse Source

Issue #1257 - Part 2: Remove watch/unwatch and JS watchpoint class.

pull/24/head
wolfbeast 3 years ago committed by Roy Tam
parent
commit
ca9e376e55
  1. 95
      js/src/builtin/Object.cpp
  2. 7
      js/src/gc/Marking.cpp
  3. 1
      js/src/gc/RootMarking.cpp
  4. 6
      js/src/jit/BaselineIC.cpp
  5. 5
      js/src/jit/IonCaches.cpp
  6. 2
      js/src/js.msg
  7. 1
      js/src/jsapi.cpp
  8. 1
      js/src/jscntxt.cpp
  9. 12
      js/src/jscompartment.cpp
  10. 3
      js/src/jscompartment.h
  11. 2
      js/src/jsfriendapi.cpp
  12. 24
      js/src/jsfriendapi.h
  13. 14
      js/src/jsgc.cpp
  14. 66
      js/src/jsobj.cpp
  15. 20
      js/src/jsobj.h
  16. 6
      js/src/jsobjinlines.h
  17. 1
      js/src/jsversion.h
  18. 246
      js/src/jswatchpoint.cpp
  19. 90
      js/src/jswatchpoint.h
  20. 1
      js/src/moz.build
  21. 8
      js/src/vm/NativeObject-inl.h
  22. 14
      js/src/vm/NativeObject.cpp
  23. 1
      js/src/vm/Runtime.cpp
  24. 2
      js/src/vm/Shape.h
  25. 114
      js/src/vm/TypeInference.cpp

95
js/src/builtin/Object.cpp

@ -568,97 +568,6 @@ obj_setPrototypeOf(JSContext* cx, unsigned argc, Value* vp)
return true;
}
#if JS_HAS_OBJ_WATCHPOINT
bool
js::WatchHandler(JSContext* cx, JSObject* obj_, jsid id_, const JS::Value& old,
JS::Value* nvp, void* closure)
{
RootedObject obj(cx, obj_);
RootedId id(cx, id_);
/* Avoid recursion on (obj, id) already being watched on cx. */
AutoResolving resolving(cx, obj, id, AutoResolving::WATCH);
if (resolving.alreadyStarted())
return true;
FixedInvokeArgs<3> args(cx);
args[0].set(IdToValue(id));
args[1].set(old);
args[2].set(*nvp);
RootedValue callable(cx, ObjectValue(*static_cast<JSObject*>(closure)));
RootedValue thisv(cx, ObjectValue(*obj));
RootedValue rv(cx);
if (!Call(cx, callable, thisv, args, &rv))
return false;
*nvp = rv;
return true;
}
static bool
obj_watch(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject obj(cx, ToObject(cx, args.thisv()));
if (!obj)
return false;
if (!GlobalObject::warnOnceAboutWatch(cx, obj))
return false;
if (args.length() <= 1) {
ReportMissingArg(cx, args.calleev(), 1);
return false;
}
RootedObject callable(cx, ValueToCallable(cx, args[1], args.length() - 2));
if (!callable)
return false;
RootedId propid(cx);
if (!ValueToId<CanGC>(cx, args[0], &propid))
return false;
if (!WatchProperty(cx, obj, propid, callable))
return false;
args.rval().setUndefined();
return true;
}
static bool
obj_unwatch(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject obj(cx, ToObject(cx, args.thisv()));
if (!obj)
return false;
if (!GlobalObject::warnOnceAboutWatch(cx, obj))
return false;
RootedId id(cx);
if (args.length() != 0) {
if (!ValueToId<CanGC>(cx, args[0], &id))
return false;
} else {
id = JSID_VOID;
}
if (!UnwatchProperty(cx, obj, id))
return false;
args.rval().setUndefined();
return true;
}
#endif /* JS_HAS_OBJ_WATCHPOINT */
/* ECMA 15.2.4.5. */
bool
js::obj_hasOwnProperty(JSContext* cx, unsigned argc, Value* vp)
@ -1290,10 +1199,6 @@ static const JSFunctionSpec object_methods[] = {
JS_FN(js_toString_str, obj_toString, 0,0),
JS_SELF_HOSTED_FN(js_toLocaleString_str, "Object_toLocaleString", 0, 0),
JS_SELF_HOSTED_FN(js_valueOf_str, "Object_valueOf", 0,0),
#if JS_HAS_OBJ_WATCHPOINT
JS_FN(js_watch_str, obj_watch, 2,0),
JS_FN(js_unwatch_str, obj_unwatch, 1,0),
#endif
JS_FN(js_hasOwnProperty_str, obj_hasOwnProperty, 1,0),
JS_FN(js_isPrototypeOf_str, obj_isPrototypeOf, 1,0),
JS_FN(js_propertyIsEnumerable_str, obj_propertyIsEnumerable, 1,0),

7
js/src/gc/Marking.cpp

@ -2846,10 +2846,9 @@ struct UnmarkGrayTracer : public JS::CallbackTracer
*
* There is an additional complication for certain kinds of edges that are not
* contained explicitly in the source object itself, such as from a weakmap key
* to its value, and from an object being watched by a watchpoint to the
* watchpoint's closure. These "implicit edges" are represented in some other
* container object, such as the weakmap or the watchpoint itself. In these
* cases, calling unmark gray on an object won't find all of its children.
* to its value. These "implicit edges" are represented in some other
* container object, such as the weakmap itself. In these cases, calling unmark
* gray on an object won't find all of its children.
*
* Handling these implicit edges has two parts:
* - A special pass enumerating all of the containers that know about the

1
js/src/gc/RootMarking.cpp

@ -14,7 +14,6 @@
#include "jsgc.h"
#include "jsprf.h"
#include "jstypes.h"
#include "jswatchpoint.h"
#include "builtin/MapObject.h"
#include "frontend/BytecodeCompiler.h"

6
js/src/jit/BaselineIC.cpp

@ -4053,9 +4053,6 @@ TryAttachSetValuePropStub(JSContext* cx, HandleScript script, jsbytecode* pc, IC
{
MOZ_ASSERT(!*attached);
if (obj->watched())
return true;
RootedShape shape(cx);
RootedObject holder(cx);
if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape))
@ -4151,9 +4148,6 @@ TryAttachSetAccessorPropStub(JSContext* cx, HandleScript script, jsbytecode* pc,
MOZ_ASSERT(!*attached);
MOZ_ASSERT(!*isTemporarilyUnoptimizable);
if (obj->watched())
return true;
RootedShape shape(cx);
RootedObject holder(cx);
if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape))

5
js/src/jit/IonCaches.cpp

@ -3232,7 +3232,7 @@ SetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript*
MOZ_ASSERT(!*emitted);
MOZ_ASSERT(!*tryNativeAddSlot);
if (!canAttachStub() || obj->watched())
if (!canAttachStub())
return true;
// Fail cache emission if the object is frozen
@ -3897,9 +3897,6 @@ IsDenseElementSetInlineable(JSObject* obj, const Value& idval, const ConstantOrR
if (!obj->is<ArrayObject>())
return false;
if (obj->watched())
return false;
if (!idval.isInt32())
return false;

2
js/src/js.msg

@ -47,7 +47,6 @@ MSG_DEF(JSMSG_MORE_ARGS_NEEDED, 3, JSEXN_TYPEERR, "{0} requires more than
MSG_DEF(JSMSG_INCOMPATIBLE_PROTO, 3, JSEXN_TYPEERR, "{0}.prototype.{1} called on incompatible {2}")
MSG_DEF(JSMSG_NO_CONSTRUCTOR, 1, JSEXN_TYPEERR, "{0} has no constructor")
MSG_DEF(JSMSG_BAD_SORT_ARG, 0, JSEXN_TYPEERR, "invalid Array.prototype.sort argument")
MSG_DEF(JSMSG_CANT_WATCH, 1, JSEXN_TYPEERR, "can't watch non-native objects of class {0}")
MSG_DEF(JSMSG_READ_ONLY, 1, JSEXN_TYPEERR, "{0} is read-only")
MSG_DEF(JSMSG_CANT_DELETE, 1, JSEXN_TYPEERR, "property {0} is non-configurable and can't be deleted")
MSG_DEF(JSMSG_CANT_TRUNCATE_ARRAY, 0, JSEXN_TYPEERR, "can't delete non-configurable array element")
@ -72,7 +71,6 @@ MSG_DEF(JSMSG_UNDEFINED_PROP, 1, JSEXN_REFERENCEERR, "reference to unde
MSG_DEF(JSMSG_INVALID_MAP_ITERABLE, 1, JSEXN_TYPEERR, "iterable for {0} should have array-like objects")
MSG_DEF(JSMSG_NESTING_GENERATOR, 0, JSEXN_TYPEERR, "already executing generator")
MSG_DEF(JSMSG_INCOMPATIBLE_METHOD, 3, JSEXN_TYPEERR, "{0} {1} called on incompatible {2}")
MSG_DEF(JSMSG_OBJECT_WATCH_DEPRECATED, 0, JSEXN_WARN, "Object.prototype.watch and unwatch are very slow, non-standard, and deprecated; use a getter/setter instead")
MSG_DEF(JSMSG_ARRAYBUFFER_SLICE_DEPRECATED, 0, JSEXN_WARN, "ArrayBuffer.slice is deprecated; use ArrayBuffer.prototype.slice instead")
MSG_DEF(JSMSG_BAD_SURROGATE_CHAR, 1, JSEXN_TYPEERR, "bad surrogate character {0}")
MSG_DEF(JSMSG_UTF8_CHAR_TOO_LARGE, 1, JSEXN_TYPEERR, "UTF-8 character {0} too large")

1
js/src/jsapi.cpp

@ -39,7 +39,6 @@
#include "jsstr.h"
#include "jstypes.h"
#include "jsutil.h"
#include "jswatchpoint.h"
#include "jsweakmap.h"
#include "jswrapper.h"

1
js/src/jscntxt.cpp

@ -37,7 +37,6 @@
#include "jsscript.h"
#include "jsstr.h"
#include "jstypes.h"
#include "jswatchpoint.h"
#include "gc/Marking.h"
#include "jit/Ion.h"

12
js/src/jscompartment.cpp

@ -13,7 +13,6 @@
#include "jsfriendapi.h"
#include "jsgc.h"
#include "jsiter.h"
#include "jswatchpoint.h"
#include "jswrapper.h"
#include "gc/Marking.h"
@ -76,7 +75,6 @@ JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options =
gcIncomingGrayPointers(nullptr),
debugModeBits(0),
randomKeyGenerator_(runtime_->forkRandomKeyGenerator()),
watchpointMap(nullptr),
scriptCountsMap(nullptr),
debugScriptMap(nullptr),
debugEnvs(nullptr),
@ -103,7 +101,6 @@ JSCompartment::~JSCompartment()
rt->lcovOutput.writeLCovResult(lcovOutput);
js_delete(jitCompartment_);
js_delete(watchpointMap);
js_delete(scriptCountsMap);
js_delete(debugScriptMap);
js_delete(debugEnvs);
@ -662,12 +659,6 @@ JSCompartment::traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime t
if (traceOrMark == js::gc::GCRuntime::MarkRuntime && !zone()->isCollecting())
return;
// During a GC, these are treated as weak pointers.
if (traceOrMark == js::gc::GCRuntime::TraceRuntime) {
if (watchpointMap)
watchpointMap->markAll(trc);
}
/* Mark debug scopes, if present */
if (debugEnvs)
debugEnvs->mark(trc);
@ -712,9 +703,6 @@ JSCompartment::traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime t
void
JSCompartment::finishRoots()
{
if (watchpointMap)
watchpointMap->clear();
if (debugEnvs)
debugEnvs->finish();

3
js/src/jscompartment.h

@ -282,7 +282,6 @@ class MOZ_RAII AutoSetNewObjectMetadata : private JS::CustomAutoRooter
namespace js {
class DebugEnvironments;
class ObjectWeakMap;
class WatchpointMap;
class WeakMapBase;
} // namespace js
@ -811,8 +810,6 @@ struct JSCompartment
void sweepBreakpoints(js::FreeOp* fop);
public:
js::WatchpointMap* watchpointMap;
js::ScriptCountsMap* scriptCountsMap;
js::DebugScriptMap* debugScriptMap;

2
js/src/jsfriendapi.cpp

@ -15,7 +15,6 @@
#include "jsgc.h"
#include "jsobj.h"
#include "jsprf.h"
#include "jswatchpoint.h"
#include "jsweakmap.h"
#include "jswrapper.h"
@ -579,7 +578,6 @@ void
js::TraceWeakMaps(WeakMapTracer* trc)
{
WeakMapBase::traceAllMappings(trc);
WatchpointMap::traceAll(trc);
}
extern JS_FRIEND_API(bool)

24
js/src/jsfriendapi.h

@ -2110,30 +2110,6 @@ JS_FRIEND_API(void*)
JS_GetDataViewData(JSObject* obj, const JS::AutoCheckCannotGC&);
namespace js {
/**
* Add a watchpoint -- in the Object.prototype.watch sense -- to |obj| for the
* property |id|, using the callable object |callable| as the function to be
* called for notifications.
*
* This is an internal function exposed -- temporarily -- only so that DOM
* proxies can be watchable. Don't use it! We'll soon kill off the
* Object.prototype.{,un}watch functions, at which point this will go too.
*/
extern JS_FRIEND_API(bool)
WatchGuts(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable);
/**
* Remove a watchpoint -- in the Object.prototype.watch sense -- from |obj| for
* the property |id|.
*
* This is an internal function exposed -- temporarily -- only so that DOM
* proxies can be watchable. Don't use it! We'll soon kill off the
* Object.prototype.{,un}watch functions, at which point this will go too.
*/
extern JS_FRIEND_API(bool)
UnwatchGuts(JSContext* cx, JS::HandleObject obj, JS::HandleId id);
namespace jit {
enum class InlinableNative : uint16_t;

14
js/src/jsgc.cpp

@ -207,7 +207,6 @@
#include "jsscript.h"
#include "jstypes.h"
#include "jsutil.h"
#include "jswatchpoint.h"
#include "jsweakmap.h"
#ifdef XP_WIN
# include "jswin.h"
@ -2392,11 +2391,6 @@ GCRuntime::updatePointersToRelocatedCells(Zone* zone, AutoLockForExclusiveAccess
Debugger::markIncomingCrossCompartmentEdges(&trc);
WeakMapBase::markAll(zone, &trc);
for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
c->trace(&trc);
if (c->watchpointMap)
c->watchpointMap->markAll(&trc);
}
// Mark all gray roots, making sure we call the trace callback to get the
// current set.
@ -2405,7 +2399,6 @@ GCRuntime::updatePointersToRelocatedCells(Zone* zone, AutoLockForExclusiveAccess
}
// Sweep everything to fix up weak pointers
WatchpointMap::sweepAll(rt);
Debugger::sweepAll(rt->defaultFreeOp());
jit::JitRuntime::SweepJitcodeGlobalTable(rt);
rt->gc.sweepZoneAfterCompacting(zone);
@ -3850,10 +3843,6 @@ GCRuntime::markWeakReferences(gcstats::Phase phase)
for (ZoneIterT zone(rt); !zone.done(); zone.next())
markedAny |= WeakMapBase::markZoneIteratively(zone, &marker);
}
for (CompartmentsIterT<ZoneIterT> c(rt); !c.done(); c.next()) {
if (c->watchpointMap)
markedAny |= c->watchpointMap->markIteratively(&marker);
}
markedAny |= Debugger::markAllIteratively(&marker);
markedAny |= jit::JitRuntime::MarkJitcodeGlobalTableIteratively(&marker);
@ -4625,9 +4614,6 @@ GCRuntime::beginSweepingZoneGroup(AutoLockForExclusiveAccess& lock)
// Bug 1071218: the following two methods have not yet been
// refactored to work on a single zone-group at once.
// Collect watch points associated with unreachable objects.
WatchpointMap::sweepAll(rt);
// Detach unreachable debuggers and global objects from each other.
Debugger::sweepAll(&fop);

66
js/src/jsobj.cpp

@ -33,7 +33,6 @@
#include "jsstr.h"
#include "jstypes.h"
#include "jsutil.h"
#include "jswatchpoint.h"
#include "jswin.h"
#include "jswrapper.h"
@ -1011,13 +1010,7 @@ js::CreateThisForFunction(JSContext* cx, HandleObject callee, HandleObject newTa
JSObject::nonNativeSetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
HandleValue receiver, ObjectOpResult& result)
{
RootedValue value(cx, v);
if (MOZ_UNLIKELY(obj->watched())) {
WatchpointMap* wpmap = cx->compartment()->watchpointMap;
if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, &value))
return false;
}
return obj->getOpsSetProperty()(cx, obj, id, value, receiver, result);
return obj->getOpsSetProperty()(cx, obj, id, v, receiver, result);
}
/* static */ bool
@ -2795,62 +2788,6 @@ js::GetPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
return true;
}
bool
js::WatchGuts(JSContext* cx, JS::HandleObject origObj, JS::HandleId id, JS::HandleObject callable)
{
RootedObject obj(cx, ToWindowIfWindowProxy(origObj));
if (obj->isNative()) {
// Use sparse indexes for watched objects, as dense elements can be
// written to without checking the watchpoint map.
if (!NativeObject::sparsifyDenseElements(cx, obj.as<NativeObject>()))
return false;
MarkTypePropertyNonData(cx, obj, id);
}
WatchpointMap* wpmap = cx->compartment()->watchpointMap;
if (!wpmap) {
wpmap = cx->runtime()->new_<WatchpointMap>();
if (!wpmap || !wpmap->init()) {
ReportOutOfMemory(cx);
js_delete(wpmap);
return false;
}
cx->compartment()->watchpointMap = wpmap;
}
return wpmap->watch(cx, obj, id, js::WatchHandler, callable);
}
bool
js::UnwatchGuts(JSContext* cx, JS::HandleObject origObj, JS::HandleId id)
{
// Looking in the map for an unsupported object will never hit, so we don't
// need to check for nativeness or watchable-ness here.
RootedObject obj(cx, ToWindowIfWindowProxy(origObj));
if (WatchpointMap* wpmap = cx->compartment()->watchpointMap)
wpmap->unwatch(obj, id, nullptr, nullptr);
return true;
}
bool
js::WatchProperty(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable)
{
if (!obj->isNative() || obj->is<TypedArrayObject>()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_WATCH,
obj->getClass()->name);
return false;
}
return WatchGuts(cx, obj, id, callable);
}
bool
js::UnwatchProperty(JSContext* cx, HandleObject obj, HandleId id)
{
return UnwatchGuts(cx, obj, id);
}
const char*
js::GetObjectClassName(JSContext* cx, HandleObject obj)
{
@ -3410,7 +3347,6 @@ JSObject::dump(FILE* fp) const
if (obj->isBoundFunction()) fprintf(fp, " bound_function");
if (obj->isQualifiedVarObj()) fprintf(fp, " varobj");
if (obj->isUnqualifiedVarObj()) fprintf(fp, " unqualified_varobj");
if (obj->watched()) fprintf(fp, " watched");
if (obj->isIteratedSingleton()) fprintf(fp, " iterated_singleton");
if (obj->isNewGroupUnknown()) fprintf(fp, " new_type_unknown");
if (obj->hasUncacheableProto()) fprintf(fp, " has_uncacheable_proto");

20
js/src/jsobj.h

@ -219,11 +219,6 @@ class JSObject : public js::gc::Cell
inline bool isBoundFunction() const;
inline bool hasSpecialEquality() const;
inline bool watched() const;
static bool setWatched(js::ExclusiveContext* cx, JS::HandleObject obj) {
return setFlags(cx, obj, js::BaseShape::WATCHED, GENERATE_SHAPE);
}
// A "qualified" varobj is the object on which "qualified" variable
// declarations (i.e., those defined with "var") are kept.
//
@ -1030,21 +1025,6 @@ extern bool
DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs,
DefineAsIntrinsic intrinsic);
/*
* Set a watchpoint: a synchronous callback when the given property of the
* given object is set.
*
* Watchpoints are nonstandard and do not fit in well with the way ES6
* specifies [[Set]]. They are also insufficient for implementing
* Object.observe.
*/
extern bool
WatchProperty(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable);
/* Clear a watchpoint. */
extern bool
UnwatchProperty(JSContext* cx, HandleObject obj, HandleId id);
/* ES6 draft rev 36 (2015 March 17) 7.1.1 ToPrimitive(vp[, preferredType]) */
extern bool
ToPrimitiveSlow(JSContext* cx, JSType hint, MutableHandleValue vp);

6
js/src/jsobjinlines.h

@ -463,12 +463,6 @@ JSObject::isBoundFunction() const
return is<JSFunction>() && as<JSFunction>().isBoundFunction();
}
inline bool
JSObject::watched() const
{
return hasAllFlags(js::BaseShape::WATCHED);
}
inline bool
JSObject::isDelegate() const
{

1
js/src/jsversion.h

@ -12,7 +12,6 @@
*/
#define JS_HAS_STR_HTML_HELPERS 1 /* (no longer used) */
#define JS_HAS_OBJ_PROTO_PROP 1 /* has o.__proto__ etc. */
#define JS_HAS_OBJ_WATCHPOINT 1 /* has o.watch and o.unwatch */
#define JS_HAS_TOSOURCE 1 /* has Object/Array toSource method */
#define JS_HAS_CATCH_GUARD 1 /* has exception handling catch guard */
#define JS_HAS_UNEVAL 1 /* has uneval() top-level function */

246
js/src/jswatchpoint.cpp

@ -1,246 +0,0 @@
/* -*- 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,
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();
JSObject* object = entry.key().object;
jsid id = entry.key().id;
JSObject* priorObject = object;
jsid priorId = id;
MOZ_ASSERT(JSID_IS_STRING(priorId) || JSID_IS_INT(priorId) || JSID_IS_SYMBOL(priorId));
TraceManuallyBarrieredEdge(trc, &object, "held Watchpoint object");
TraceManuallyBarrieredEdge(trc, &id, "WatchKey::id");
TraceEdge(trc, &entry.value().closure, "Watchpoint::closure");
if (priorObject != object || priorId != id)
e.rekeyFront(WatchKey(object, id));
}
}
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()));
}
}

90
js/src/jswatchpoint.h

@ -1,90 +0,0 @@
/* -*- 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/. */
#ifndef jswatchpoint_h
#define jswatchpoint_h
#include "jsalloc.h"
#include "gc/Barrier.h"
#include "js/HashTable.h"
namespace js {
struct WeakMapTracer;
struct WatchKey {
WatchKey() {}
WatchKey(JSObject* obj, jsid id) : object(obj), id(id) {}
WatchKey(const WatchKey& key) : object(key.object.get()), id(key.id.get()) {}
// These are traced unconditionally during minor GC, so do not require
// post-barriers.
PreBarrieredObject object;
PreBarrieredId id;
bool operator!=(const WatchKey& other) const {
return object != other.object || id != other.id;
}
};
typedef bool
(* JSWatchPointHandler)(JSContext* cx, JSObject* obj, jsid id, const JS::Value& old,
JS::Value* newp, void* closure);
struct Watchpoint {
JSWatchPointHandler handler;
PreBarrieredObject closure; /* This is always marked in minor GCs and so doesn't require a postbarrier. */
bool held; /* true if currently running handler */
Watchpoint(JSWatchPointHandler handler, JSObject* closure, bool held)
: handler(handler), closure(closure), held(held) {}
};
struct WatchKeyHasher
{
typedef WatchKey Lookup;
static inline js::HashNumber hash(const Lookup& key);
static bool match(const WatchKey& k, const Lookup& l) {
return MovableCellHasher<PreBarrieredObject>::match(k.object, l.object) &&
DefaultHasher<PreBarrieredId>::match(k.id, l.id);
}
static void rekey(WatchKey& k, const WatchKey& newKey) {
k.object.unsafeSet(newKey.object);
k.id.unsafeSet(newKey.id);
}
};
class WatchpointMap {
public:
typedef HashMap<WatchKey, Watchpoint, WatchKeyHasher, SystemAllocPolicy> Map;
bool init();
bool watch(JSContext* cx, HandleObject obj, HandleId id,
JSWatchPointHandler handler, HandleObject closure);
void unwatch(JSObject* obj, jsid id,
JSWatchPointHandler* handlerp, JSObject** closurep);
void unwatchObject(JSObject* obj);
void clear();
bool triggerWatchpoint(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp);
bool markIteratively(JSTracer* trc);
void markAll(JSTracer* trc);
static void sweepAll(JSRuntime* rt);
void sweep();
static void traceAll(WeakMapTracer* trc);
void trace(WeakMapTracer* trc);
private:
Map map;
};
} // namespace js
#endif /* jswatchpoint_h */

1
js/src/moz.build

@ -291,7 +291,6 @@ UNIFIED_SOURCES += [
'jspropertytree.cpp',
'jsscript.cpp',
'jsstr.cpp',
'jswatchpoint.cpp',
'jsweakmap.cpp',
'perf/jsperf.cpp',
'proxy/BaseProxyHandler.cpp',

8
js/src/vm/NativeObject-inl.h

@ -158,11 +158,11 @@ NativeObject::extendDenseElements(ExclusiveContext* cx,
MOZ_ASSERT(!denseElementsAreFrozen());
/*
* Don't grow elements for non-extensible objects or watched objects. Dense
* elements can be added/written with no extensible or watchpoint checks as
* long as there is capacity for them.
* Don't grow elements for non-extensible objects. Dense elements can be
* added/written with no extensible checks as long as there is capacity
* for them.
*/
if (!nonProxyIsExtensible() || watched()) {
if (!nonProxyIsExtensible()) {
MOZ_ASSERT(getDenseCapacity() == 0);
return DenseElementResult::Incomplete;
}

14
js/src/vm/NativeObject.cpp

@ -9,8 +9,6 @@
#include "mozilla/ArrayUtils.h"
#include "mozilla/Casting.h"
#include "jswatchpoint.h"
#include "gc/Marking.h"
#include "js/Value.h"
#include "vm/Debugger.h"
@ -602,7 +600,7 @@ NativeObject::maybeDensifySparseElements(js::ExclusiveContext* cx, HandleNativeO
return DenseElementResult::Incomplete;
/* Watch for conditions under which an object's elements cannot be dense. */
if (!obj->nonProxyIsExtensible() || obj->watched())
if (!obj->nonProxyIsExtensible())
return DenseElementResult::Incomplete;
/*
@ -2410,17 +2408,9 @@ SetExistingProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleVa
}
bool
js::NativeSetProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleValue value,
js::NativeSetProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleValue v,
HandleValue receiver, QualifiedBool qualified, ObjectOpResult& result)
{
// Fire watchpoints, if any.
RootedValue v(cx, value);
if (MOZ_UNLIKELY(obj->watched())) {
WatchpointMap* wpmap = cx->compartment()->watchpointMap;
if (wpmap && !wpmap->triggerWatchpoint(cx, obj, id, &v))
return false;
}
// Step numbers below reference ES6 rev 27 9.1.9, the [[Set]] internal
// method for ordinary objects. We substitute our own names for these names
// used in the spec: O -> pobj, P -> id, ownDesc -> shape.

1
js/src/vm/Runtime.cpp

@ -34,7 +34,6 @@
#include "jsnativestack.h"
#include "jsobj.h"
#include "jsscript.h"
#include "jswatchpoint.h"
#include "jswin.h"
#include "jswrapper.h"

2
js/src/vm/Shape.h

@ -387,7 +387,7 @@ class BaseShape : public gc::TenuredCell
INDEXED = 0x20,
/* (0x40 is unused) */
HAD_ELEMENTS_ACCESS = 0x80,
WATCHED = 0x100,
/* (0x100 is unused) */
ITERATED_SINGLETON = 0x200,
NEW_GROUP_UNKNOWN = 0x400,
UNCACHEABLE_PROTO = 0x800,

114
js/src/vm/TypeInference.cpp

@ -2670,14 +2670,6 @@ ObjectGroup::updateNewPropertyTypes(ExclusiveContext* cx, JSObject* objArg, jsid
if (shape)
UpdatePropertyType(cx, types, obj, shape, false);
}
if (obj->watched()) {
/*
* Mark the property as non-data, to inhibit optimizations on it
* and avoid bypassing the watchpoint handler.
*/
types->setNonDataProperty(cx);
}
}
void
@ -3622,42 +3614,42 @@ struct DestroyTypeNewScript
} // namespace
bool DPAConstraintInfo::finishConstraints(JSContext* cx, ObjectGroup* group) {
for (const ProtoConstraint& constraint : protoConstraints_) {
ObjectGroup* protoGroup = constraint.proto->group();
// Note: we rely on the group's type information being unchanged since
// AddClearDefiniteGetterSetterForPrototypeChain.
bool unknownProperties = protoGroup->unknownProperties();
MOZ_RELEASE_ASSERT(!unknownProperties);
HeapTypeSet* protoTypes =
protoGroup->getProperty(cx, constraint.proto, constraint.id);
MOZ_RELEASE_ASSERT(protoTypes);
MOZ_ASSERT(!protoTypes->nonDataProperty());
MOZ_ASSERT(!protoTypes->nonWritableProperty());
if (!protoTypes->addConstraint(
cx,
cx->typeLifoAlloc().new_<TypeConstraintClearDefiniteGetterSetter>(
group))) {
ReportOutOfMemory(cx);
return false;
}
}
for (const InliningConstraint& constraint : inliningConstraints_) {
if (!AddClearDefiniteFunctionUsesInScript(cx, group, constraint.caller,
constraint.callee)) {
ReportOutOfMemory(cx);
return false;
}
}
return true;
}
bool DPAConstraintInfo::finishConstraints(JSContext* cx, ObjectGroup* group) {
for (const ProtoConstraint& constraint : protoConstraints_) {
ObjectGroup* protoGroup = constraint.proto->group();
// Note: we rely on the group's type information being unchanged since
// AddClearDefiniteGetterSetterForPrototypeChain.
bool unknownProperties = protoGroup->unknownProperties();
MOZ_RELEASE_ASSERT(!unknownProperties);
HeapTypeSet* protoTypes =
protoGroup->getProperty(cx, constraint.proto, constraint.id);
MOZ_RELEASE_ASSERT(protoTypes);
MOZ_ASSERT(!protoTypes->nonDataProperty());
MOZ_ASSERT(!protoTypes->nonWritableProperty());
if (!protoTypes->addConstraint(
cx,
cx->typeLifoAlloc().new_<TypeConstraintClearDefiniteGetterSetter>(
group))) {
ReportOutOfMemory(cx);
return false;
}
}
for (const InliningConstraint& constraint : inliningConstraints_) {
if (!AddClearDefiniteFunctionUsesInScript(cx, group, constraint.caller,
constraint.callee)) {
ReportOutOfMemory(cx);
return false;
}
}
return true;
}
bool
TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate, bool force)
@ -3826,13 +3818,13 @@ TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate,
// The definite properties analysis found exactly the properties that
// are held in common by the preliminary objects. No further analysis
// is needed.
if (!constraintInfo.finishConstraints(cx, group)) {
return false;
}
if (!group->newScript()) {
return true;
}
if (!constraintInfo.finishConstraints(cx, group)) {
return false;
}
if (!group->newScript()) {
return true;
}
group->addDefiniteProperties(cx, templateObject()->lastProperty());
@ -3854,16 +3846,16 @@ TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate,
initialFlags);
if (!initialGroup)
return false;
// Add the constraints. Use the initialGroup as group referenced by the
// constraints because that's the group that will have the TypeNewScript
// associated with it. See the detachNewScript and setNewScript calls below.
if (!constraintInfo.finishConstraints(cx, initialGroup)) {
return false;
}
if (!group->newScript()) {
return true;
}
// Add the constraints. Use the initialGroup as group referenced by the
// constraints because that's the group that will have the TypeNewScript
// associated with it. See the detachNewScript and setNewScript calls below.
if (!constraintInfo.finishConstraints(cx, initialGroup)) {
return false;
}
if (!group->newScript()) {
return true;
}
initialGroup->addDefiniteProperties(cx, templateObject()->lastProperty());
group->addDefiniteProperties(cx, prefixShape);

Loading…
Cancel
Save