1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "jswatchpoint.h"
8
9 #include "jsatom.h"
10 #include "jscompartment.h"
11 #include "jsfriendapi.h"
12
13 #include "gc/Marking.h"
14 #include "vm/Shape.h"
15
16 #include "jsgcinlines.h"
17
18 using namespace js;
19 using namespace js::gc;
20
21 inline HashNumber
hash(const Lookup & key)22 WatchKeyHasher::hash(const Lookup& key)
23 {
24 return MovableCellHasher<PreBarrieredObject>::hash(key.object) ^ HashId(key.id);
25 }
26
27 namespace {
28
29 class AutoEntryHolder {
30 typedef WatchpointMap::Map Map;
31 Generation gen;
32 Map& map;
33 Map::Ptr p;
34 RootedObject obj;
35 RootedId id;
36
37 public:
AutoEntryHolder(JSContext * cx,Map & map,Map::Ptr p)38 AutoEntryHolder(JSContext* cx, Map& map, Map::Ptr p)
39 : gen(map.generation()), map(map), p(p), obj(cx, p->key().object), id(cx, p->key().id)
40 {
41 MOZ_ASSERT(!p->value().held);
42 p->value().held = true;
43 }
44
~AutoEntryHolder()45 ~AutoEntryHolder() {
46 if (gen != map.generation())
47 p = map.lookup(WatchKey(obj, id));
48 if (p)
49 p->value().held = false;
50 }
51 };
52
53 } /* anonymous namespace */
54
55 bool
init()56 WatchpointMap::init()
57 {
58 return map.init();
59 }
60
61 bool
watch(JSContext * cx,HandleObject obj,HandleId id,JSWatchPointHandler handler,HandleObject closure)62 WatchpointMap::watch(JSContext* cx, HandleObject obj, HandleId id,
63 JSWatchPointHandler handler, HandleObject closure)
64 {
65 MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id) || JSID_IS_SYMBOL(id));
66
67 if (!obj->setWatched(cx))
68 return false;
69
70 Watchpoint w(handler, closure, false);
71 if (!map.put(WatchKey(obj, id), w)) {
72 ReportOutOfMemory(cx);
73 return false;
74 }
75 /*
76 * For generational GC, we don't need to post-barrier writes to the
77 * hashtable here because we mark all watchpoints as part of root marking in
78 * markAll().
79 */
80 return true;
81 }
82
83 void
unwatch(JSObject * obj,jsid id,JSWatchPointHandler * handlerp,JSObject ** closurep)84 WatchpointMap::unwatch(JSObject* obj, jsid id,
85 JSWatchPointHandler* handlerp, JSObject** closurep)
86 {
87 if (Map::Ptr p = map.lookup(WatchKey(obj, id))) {
88 if (handlerp)
89 *handlerp = p->value().handler;
90 if (closurep) {
91 // Read barrier to prevent an incorrectly gray closure from escaping the
92 // watchpoint. See the comment before UnmarkGrayChildren in gc/Marking.cpp
93 JS::ExposeObjectToActiveJS(p->value().closure);
94 *closurep = p->value().closure;
95 }
96 map.remove(p);
97 }
98 }
99
100 void
unwatchObject(JSObject * obj)101 WatchpointMap::unwatchObject(JSObject* obj)
102 {
103 for (Map::Enum e(map); !e.empty(); e.popFront()) {
104 Map::Entry& entry = e.front();
105 if (entry.key().object == obj)
106 e.removeFront();
107 }
108 }
109
110 void
clear()111 WatchpointMap::clear()
112 {
113 map.clear();
114 }
115
116 bool
triggerWatchpoint(JSContext * cx,HandleObject obj,HandleId id,MutableHandleValue vp)117 WatchpointMap::triggerWatchpoint(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)
118 {
119 Map::Ptr p = map.lookup(WatchKey(obj, id));
120 if (!p || p->value().held)
121 return true;
122
123 AutoEntryHolder holder(cx, map, p);
124
125 /* Copy the entry, since GC would invalidate p. */
126 JSWatchPointHandler handler = p->value().handler;
127 RootedObject closure(cx, p->value().closure);
128
129 /* Determine the property's old value. */
130 Value old;
131 old.setUndefined();
132 if (obj->isNative()) {
133 NativeObject* nobj = &obj->as<NativeObject>();
134 if (Shape* shape = nobj->lookup(cx, id)) {
135 if (shape->hasSlot())
136 old = nobj->getSlot(shape->slot());
137 }
138 }
139
140 // Read barrier to prevent an incorrectly gray closure from escaping the
141 // watchpoint. See the comment before UnmarkGrayChildren in gc/Marking.cpp
142 JS::ExposeObjectToActiveJS(closure);
143
144 /* Call the handler. */
145 return handler(cx, obj, id, old, vp.address(), closure);
146 }
147
148 bool
markIteratively(JSTracer * trc)149 WatchpointMap::markIteratively(JSTracer* trc)
150 {
151 bool marked = false;
152 for (Map::Enum e(map); !e.empty(); e.popFront()) {
153 Map::Entry& entry = e.front();
154 JSObject* priorKeyObj = entry.key().object;
155 jsid priorKeyId(entry.key().id.get());
156 bool objectIsLive =
157 IsMarked(trc->runtime(), const_cast<PreBarrieredObject*>(&entry.key().object));
158 if (objectIsLive || entry.value().held) {
159 if (!objectIsLive) {
160 TraceEdge(trc, const_cast<PreBarrieredObject*>(&entry.key().object),
161 "held Watchpoint object");
162 marked = true;
163 }
164
165 MOZ_ASSERT(JSID_IS_STRING(priorKeyId) ||
166 JSID_IS_INT(priorKeyId) ||
167 JSID_IS_SYMBOL(priorKeyId));
168 TraceEdge(trc, const_cast<PreBarrieredId*>(&entry.key().id), "WatchKey::id");
169
170 if (entry.value().closure && !IsMarked(trc->runtime(), &entry.value().closure)) {
171 TraceEdge(trc, &entry.value().closure, "Watchpoint::closure");
172 marked = true;
173 }
174
175 /* We will sweep this entry in sweepAll if !objectIsLive. */
176 if (priorKeyObj != entry.key().object || priorKeyId != entry.key().id)
177 e.rekeyFront(WatchKey(entry.key().object, entry.key().id));
178 }
179 }
180 return marked;
181 }
182
183 void
markAll(JSTracer * trc)184 WatchpointMap::markAll(JSTracer* trc)
185 {
186 for (Map::Enum e(map); !e.empty(); e.popFront()) {
187 Map::Entry& entry = e.front();
188 WatchKey key = entry.key();
189 WatchKey prior = key;
190 MOZ_ASSERT(JSID_IS_STRING(prior.id) || JSID_IS_INT(prior.id) || JSID_IS_SYMBOL(prior.id));
191
192 TraceEdge(trc, const_cast<PreBarrieredObject*>(&key.object),
193 "held Watchpoint object");
194 TraceEdge(trc, const_cast<PreBarrieredId*>(&key.id), "WatchKey::id");
195 TraceEdge(trc, &entry.value().closure, "Watchpoint::closure");
196
197 if (prior.object != key.object || prior.id != key.id)
198 e.rekeyFront(key);
199 }
200 }
201
202 void
sweepAll(JSRuntime * rt)203 WatchpointMap::sweepAll(JSRuntime* rt)
204 {
205 for (GCCompartmentsIter c(rt); !c.done(); c.next()) {
206 if (WatchpointMap* wpmap = c->watchpointMap)
207 wpmap->sweep();
208 }
209 }
210
211 void
sweep()212 WatchpointMap::sweep()
213 {
214 for (Map::Enum e(map); !e.empty(); e.popFront()) {
215 Map::Entry& entry = e.front();
216 JSObject* obj(entry.key().object);
217 if (IsAboutToBeFinalizedUnbarriered(&obj)) {
218 MOZ_ASSERT(!entry.value().held);
219 e.removeFront();
220 } else if (obj != entry.key().object) {
221 e.rekeyFront(WatchKey(obj, entry.key().id));
222 }
223 }
224 }
225
226 void
traceAll(WeakMapTracer * trc)227 WatchpointMap::traceAll(WeakMapTracer* trc)
228 {
229 JSRuntime* rt = trc->runtime;
230 for (CompartmentsIter comp(rt, SkipAtoms); !comp.done(); comp.next()) {
231 if (WatchpointMap* wpmap = comp->watchpointMap)
232 wpmap->trace(trc);
233 }
234 }
235
236 void
trace(WeakMapTracer * trc)237 WatchpointMap::trace(WeakMapTracer* trc)
238 {
239 for (Map::Range r = map.all(); !r.empty(); r.popFront()) {
240 Map::Entry& entry = r.front();
241 trc->trace(nullptr,
242 JS::GCCellPtr(entry.key().object.get()),
243 JS::GCCellPtr(entry.value().closure.get()));
244 }
245 }
246