Line data Source code
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
22 0 : WatchKeyHasher::hash(const Lookup& key)
23 : {
24 0 : 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:
38 0 : AutoEntryHolder(JSContext* cx, Map& map, Map::Ptr p)
39 0 : : gen(map.generation()), map(map), p(p), obj(cx, p->key().object), id(cx, p->key().id)
40 : {
41 0 : MOZ_ASSERT(!p->value().held);
42 0 : p->value().held = true;
43 0 : }
44 :
45 0 : ~AutoEntryHolder() {
46 0 : if (gen != map.generation())
47 0 : p = map.lookup(WatchKey(obj, id));
48 0 : if (p)
49 0 : p->value().held = false;
50 0 : }
51 : };
52 :
53 : } /* anonymous namespace */
54 :
55 : bool
56 0 : WatchpointMap::init()
57 : {
58 0 : return map.init();
59 : }
60 :
61 : bool
62 0 : WatchpointMap::watch(JSContext* cx, HandleObject obj, HandleId id,
63 : JSWatchPointHandler handler, HandleObject closure)
64 : {
65 0 : MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_INT(id) || JSID_IS_SYMBOL(id));
66 :
67 0 : if (!JSObject::setWatched(cx, obj))
68 0 : return false;
69 :
70 0 : Watchpoint w(handler, closure, false);
71 0 : if (!map.put(WatchKey(obj, id), w)) {
72 0 : ReportOutOfMemory(cx);
73 0 : 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 0 : return true;
81 : }
82 :
83 : void
84 0 : WatchpointMap::unwatch(JSObject* obj, jsid id)
85 : {
86 0 : if (Map::Ptr p = map.lookup(WatchKey(obj, id)))
87 0 : map.remove(p);
88 0 : }
89 :
90 : void
91 0 : WatchpointMap::unwatchObject(JSObject* obj)
92 : {
93 0 : for (Map::Enum e(map); !e.empty(); e.popFront()) {
94 0 : Map::Entry& entry = e.front();
95 0 : if (entry.key().object == obj)
96 0 : e.removeFront();
97 : }
98 0 : }
99 :
100 : void
101 0 : WatchpointMap::clear()
102 : {
103 0 : map.clear();
104 0 : }
105 :
106 : bool
107 0 : WatchpointMap::triggerWatchpoint(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)
108 : {
109 0 : Map::Ptr p = map.lookup(WatchKey(obj, id));
110 0 : if (!p || p->value().held)
111 0 : return true;
112 :
113 0 : AutoEntryHolder holder(cx, map, p);
114 :
115 : /* Copy the entry, since GC would invalidate p. */
116 0 : JSWatchPointHandler handler = p->value().handler;
117 0 : RootedObject closure(cx, p->value().closure);
118 :
119 : /* Determine the property's old value. */
120 0 : Value old;
121 0 : old.setUndefined();
122 0 : if (obj->isNative()) {
123 0 : NativeObject* nobj = &obj->as<NativeObject>();
124 0 : if (Shape* shape = nobj->lookup(cx, id)) {
125 0 : if (shape->hasSlot())
126 0 : old = nobj->getSlot(shape->slot());
127 : }
128 : }
129 :
130 : // Read barrier to prevent an incorrectly gray closure from escaping the
131 : // watchpoint. See the comment before UnmarkGrayChildren in gc/Marking.cpp
132 0 : JS::ExposeObjectToActiveJS(closure);
133 :
134 : /* Call the handler. */
135 0 : return handler(cx, obj, id, old, vp.address(), closure);
136 : }
137 :
138 : bool
139 0 : WatchpointMap::markIteratively(GCMarker* marker)
140 : {
141 0 : bool marked = false;
142 0 : for (Map::Enum e(map); !e.empty(); e.popFront()) {
143 0 : Map::Entry& entry = e.front();
144 0 : JSObject* priorKeyObj = entry.key().object;
145 0 : jsid priorKeyId(entry.key().id.get());
146 : bool objectIsLive =
147 0 : IsMarked(marker->runtime(), const_cast<PreBarrieredObject*>(&entry.key().object));
148 0 : if (objectIsLive || entry.value().held) {
149 0 : if (!objectIsLive) {
150 0 : TraceEdge(marker, const_cast<PreBarrieredObject*>(&entry.key().object),
151 0 : "held Watchpoint object");
152 0 : marked = true;
153 : }
154 :
155 0 : MOZ_ASSERT(JSID_IS_STRING(priorKeyId) ||
156 : JSID_IS_INT(priorKeyId) ||
157 : JSID_IS_SYMBOL(priorKeyId));
158 0 : TraceEdge(marker, const_cast<PreBarrieredId*>(&entry.key().id), "WatchKey::id");
159 :
160 0 : if (entry.value().closure && !IsMarked(marker->runtime(), &entry.value().closure)) {
161 0 : TraceEdge(marker, &entry.value().closure, "Watchpoint::closure");
162 0 : marked = true;
163 : }
164 :
165 : /* We will sweep this entry in sweepAll if !objectIsLive. */
166 0 : if (priorKeyObj != entry.key().object || priorKeyId != entry.key().id)
167 0 : e.rekeyFront(WatchKey(entry.key().object, entry.key().id));
168 : }
169 : }
170 0 : return marked;
171 : }
172 :
173 : void
174 0 : WatchpointMap::trace(JSTracer* trc)
175 : {
176 0 : for (Map::Enum e(map); !e.empty(); e.popFront()) {
177 0 : Map::Entry& entry = e.front();
178 0 : JSObject* object = entry.key().object;
179 0 : jsid id = entry.key().id;
180 0 : JSObject* priorObject = object;
181 0 : jsid priorId = id;
182 0 : MOZ_ASSERT(JSID_IS_STRING(priorId) || JSID_IS_INT(priorId) || JSID_IS_SYMBOL(priorId));
183 :
184 0 : TraceManuallyBarrieredEdge(trc, &object, "held Watchpoint object");
185 0 : TraceManuallyBarrieredEdge(trc, &id, "WatchKey::id");
186 0 : TraceEdge(trc, &entry.value().closure, "Watchpoint::closure");
187 :
188 0 : if (priorObject != object || priorId != id)
189 0 : e.rekeyFront(WatchKey(object, id));
190 : }
191 0 : }
192 :
193 : /* static */ void
194 0 : WatchpointMap::sweepAll(JSRuntime* rt)
195 : {
196 : // This is called during compacting GC. Watchpoint closure pointers can be
197 : // cross-compartment so we have to sweep all watchpoint maps, not just those
198 : // owned by compartments we are compacting.
199 0 : for (GCCompartmentsIter c(rt); !c.done(); c.next()) {
200 0 : if (WatchpointMap* wpmap = c->watchpointMap)
201 0 : wpmap->sweep();
202 : }
203 0 : }
204 :
205 : void
206 0 : WatchpointMap::sweep()
207 : {
208 0 : for (Map::Enum e(map); !e.empty(); e.popFront()) {
209 0 : Map::Entry& entry = e.front();
210 0 : JSObject* obj(entry.key().object);
211 0 : if (IsAboutToBeFinalizedUnbarriered(&obj)) {
212 0 : MOZ_ASSERT(!entry.value().held);
213 0 : e.removeFront();
214 0 : } else if (obj != entry.key().object) {
215 0 : e.rekeyFront(WatchKey(obj, entry.key().id));
216 : }
217 : }
218 0 : }
219 :
220 : void
221 0 : WatchpointMap::traceAll(WeakMapTracer* trc)
222 : {
223 0 : JSRuntime* rt = trc->runtime;
224 0 : for (CompartmentsIter comp(rt, SkipAtoms); !comp.done(); comp.next()) {
225 0 : if (WatchpointMap* wpmap = comp->watchpointMap)
226 0 : wpmap->trace(trc);
227 : }
228 0 : }
229 :
230 : void
231 0 : WatchpointMap::trace(WeakMapTracer* trc)
232 : {
233 0 : for (Map::Range r = map.all(); !r.empty(); r.popFront()) {
234 0 : Map::Entry& entry = r.front();
235 0 : trc->trace(nullptr,
236 0 : JS::GCCellPtr(entry.key().object.get()),
237 0 : JS::GCCellPtr(entry.value().closure.get()));
238 : }
239 0 : }
|