1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : *
3 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is an implementation of watchpoints for SpiderMonkey.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Mozilla Foundation.
20 : * Portions created by the Initial Developer are Copyright (C) 2011
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Jason Orendorff <jorendorff@mozilla.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "jswatchpoint.h"
41 : #include "jsatom.h"
42 : #include "jsgcmark.h"
43 : #include "jsobjinlines.h"
44 :
45 : using namespace js;
46 : using namespace js::gc;
47 :
48 : inline HashNumber
49 9545 : DefaultHasher<WatchKey>::hash(const Lookup &key)
50 : {
51 9545 : return DefaultHasher<JSObject *>::hash(key.object.get()) ^ HashId(key.id.get());
52 : }
53 :
54 : class AutoEntryHolder {
55 : typedef WatchpointMap::Map Map;
56 : Map ↦
57 : Map::Ptr p;
58 : uint32_t gen;
59 : WatchKey key;
60 :
61 : public:
62 3783 : AutoEntryHolder(Map &map, Map::Ptr p)
63 3783 : : map(map), p(p), gen(map.generation()), key(p->key) {
64 3783 : JS_ASSERT(!p->value.held);
65 3783 : p->value.held = true;
66 3783 : }
67 :
68 7566 : ~AutoEntryHolder() {
69 3783 : if (gen != map.generation())
70 1509 : p = map.lookup(key);
71 3783 : if (p)
72 3783 : p->value.held = false;
73 3783 : }
74 : };
75 :
76 : bool
77 225 : WatchpointMap::init()
78 : {
79 225 : return map.init();
80 : }
81 :
82 : bool
83 3630 : WatchpointMap::watch(JSContext *cx, JSObject *obj, jsid id,
84 : JSWatchPointHandler handler, JSObject *closure)
85 : {
86 3630 : JS_ASSERT(id == js_CheckForStringIndex(id));
87 3630 : JS_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id));
88 :
89 3630 : if (!obj->setWatched(cx))
90 0 : return false;
91 :
92 7260 : Watchpoint w;
93 3630 : w.handler = handler;
94 3630 : w.closure = closure;
95 3630 : w.held = false;
96 3630 : if (!map.put(WatchKey(obj, id), w)) {
97 0 : js_ReportOutOfMemory(cx);
98 0 : return false;
99 : }
100 3630 : return true;
101 : }
102 :
103 : void
104 9 : WatchpointMap::unwatch(JSObject *obj, jsid id,
105 : JSWatchPointHandler *handlerp, JSObject **closurep)
106 : {
107 9 : JS_ASSERT(id == js_CheckForStringIndex(id));
108 9 : if (Map::Ptr p = map.lookup(WatchKey(obj, id))) {
109 9 : if (handlerp)
110 0 : *handlerp = p->value.handler;
111 9 : if (closurep)
112 0 : *closurep = p->value.closure;
113 9 : map.remove(p);
114 : }
115 9 : }
116 :
117 : void
118 0 : WatchpointMap::unwatchObject(JSObject *obj)
119 : {
120 0 : for (Map::Enum r(map); !r.empty(); r.popFront()) {
121 0 : Map::Entry &e = r.front();
122 0 : if (e.key.object == obj)
123 0 : r.removeFront();
124 : }
125 0 : }
126 :
127 : void
128 216 : WatchpointMap::clear()
129 : {
130 216 : map.clear();
131 216 : }
132 :
133 : bool
134 4397 : WatchpointMap::triggerWatchpoint(JSContext *cx, JSObject *obj, jsid id, Value *vp)
135 : {
136 4397 : JS_ASSERT(id == js_CheckForStringIndex(id));
137 4397 : Map::Ptr p = map.lookup(WatchKey(obj, id));
138 4397 : if (!p || p->value.held)
139 614 : return true;
140 :
141 7566 : AutoEntryHolder holder(map, p);
142 :
143 : /* Copy the entry, since GC would invalidate p. */
144 3783 : JSWatchPointHandler handler = p->value.handler;
145 3783 : JSObject *closure = p->value.closure;
146 :
147 : /* Determine the property's old value. */
148 : Value old;
149 3783 : old.setUndefined();
150 3783 : if (obj->isNative()) {
151 3783 : if (const Shape *shape = obj->nativeLookup(cx, id)) {
152 702 : if (shape->hasSlot()) {
153 675 : if (shape->isMethod()) {
154 : /*
155 : * The existing watched property is a method. Trip
156 : * the method read barrier in order to avoid
157 : * passing an uncloned function object to the
158 : * handler.
159 : */
160 0 : old = UndefinedValue();
161 0 : Value method = ObjectValue(*obj->nativeGetMethod(shape));
162 0 : if (!obj->methodReadBarrier(cx, *shape, &method))
163 0 : return false;
164 0 : shape = obj->nativeLookup(cx, id);
165 0 : JS_ASSERT(shape->isDataDescriptor());
166 0 : JS_ASSERT(!shape->isMethod());
167 0 : old = method;
168 : } else {
169 675 : old = obj->nativeGetSlot(shape->slot());
170 : }
171 : }
172 : }
173 : }
174 :
175 : /* Call the handler. */
176 3783 : return handler(cx, obj, id, old, vp, closure);
177 : }
178 :
179 : bool
180 102504 : WatchpointMap::markAllIteratively(JSTracer *trc)
181 : {
182 102504 : JSRuntime *rt = trc->runtime;
183 102504 : if (rt->gcCurrentCompartment) {
184 486 : WatchpointMap *wpmap = rt->gcCurrentCompartment->watchpointMap;
185 486 : return wpmap && wpmap->markIteratively(trc);
186 : }
187 :
188 102018 : bool mutated = false;
189 347748 : for (CompartmentsIter c(rt); !c.done(); c.next()) {
190 245730 : if (c->watchpointMap)
191 468 : mutated |= c->watchpointMap->markIteratively(trc);
192 : }
193 102018 : return mutated;
194 : }
195 :
196 : bool
197 468 : WatchpointMap::markIteratively(JSTracer *trc)
198 : {
199 468 : bool marked = false;
200 504 : for (Map::Range r = map.all(); !r.empty(); r.popFront()) {
201 36 : Map::Entry &e = r.front();
202 36 : bool objectIsLive = !IsAboutToBeFinalized(e.key.object);
203 36 : if (objectIsLive || e.value.held) {
204 18 : if (!objectIsLive) {
205 0 : HeapPtrObject tmp(e.key.object);
206 0 : MarkObject(trc, &tmp, "held Watchpoint object");
207 0 : JS_ASSERT(tmp == e.key.object);
208 0 : marked = true;
209 : }
210 :
211 18 : const HeapId &id = e.key.id;
212 18 : JS_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id));
213 36 : HeapId tmp(id.get());
214 18 : MarkId(trc, &tmp, "WatchKey::id");
215 18 : JS_ASSERT(tmp.get() == id.get());
216 :
217 18 : if (e.value.closure && IsAboutToBeFinalized(e.value.closure)) {
218 0 : MarkObject(trc, &e.value.closure, "Watchpoint::closure");
219 0 : marked = true;
220 : }
221 : }
222 : }
223 468 : return marked;
224 : }
225 :
226 : void
227 0 : WatchpointMap::markAll(JSTracer *trc)
228 : {
229 0 : for (Map::Range r = map.all(); !r.empty(); r.popFront()) {
230 0 : Map::Entry &e = r.front();
231 0 : HeapPtrObject tmpObj(e.key.object);
232 0 : MarkObject(trc, &tmpObj, "held Watchpoint object");
233 0 : JS_ASSERT(tmpObj == e.key.object);
234 :
235 0 : const HeapId &id = e.key.id;
236 0 : JS_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id));
237 0 : HeapId tmpId(id.get());
238 0 : MarkId(trc, &tmpId, "WatchKey::id");
239 0 : JS_ASSERT(tmpId.get() == id.get());
240 :
241 0 : MarkObject(trc, &e.value.closure, "Watchpoint::closure");
242 : }
243 0 : }
244 :
245 : void
246 51092 : WatchpointMap::sweepAll(JSRuntime *rt)
247 : {
248 51092 : if (rt->gcCurrentCompartment) {
249 216 : if (WatchpointMap *wpmap = rt->gcCurrentCompartment->watchpointMap)
250 0 : wpmap->sweep();
251 : } else {
252 173138 : for (CompartmentsIter c(rt); !c.done(); c.next()) {
253 122262 : if (WatchpointMap *wpmap = c->watchpointMap)
254 234 : wpmap->sweep();
255 : }
256 : }
257 51092 : }
258 :
259 : void
260 234 : WatchpointMap::sweep()
261 : {
262 252 : for (Map::Enum r(map); !r.empty(); r.popFront()) {
263 18 : Map::Entry &e = r.front();
264 18 : if (IsAboutToBeFinalized(e.key.object)) {
265 9 : JS_ASSERT(!e.value.held);
266 9 : r.removeFront();
267 : }
268 : }
269 234 : }
270 :
271 : void
272 1910 : WatchpointMap::traceAll(WeakMapTracer *trc)
273 : {
274 1910 : JSRuntime *rt = trc->runtime;
275 7668 : for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c) {
276 5758 : if (WatchpointMap *wpmap = (*c)->watchpointMap)
277 0 : wpmap->trace(trc);
278 : }
279 1910 : }
280 :
281 : void
282 0 : WatchpointMap::trace(WeakMapTracer *trc)
283 : {
284 0 : for (Map::Range r = map.all(); !r.empty(); r.popFront()) {
285 0 : Map::Entry &e = r.front();
286 : trc->callback(trc, NULL,
287 0 : e.key.object.get(), JSTRACE_OBJECT,
288 0 : e.value.closure.get(), JSTRACE_OBJECT);
289 : }
290 0 : }
|