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