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