js/src/jsweakmap.cpp

branch
TOR_BUG_3246
changeset 7
129ffea94266
equal deleted inserted replaced
-1:000000000000 0:15f0b9bcf26b
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 "jsweakmap.h"
8
9 #include <string.h>
10
11 #include "jsapi.h"
12 #include "jscntxt.h"
13 #include "jsfriendapi.h"
14 #include "jsobj.h"
15 #include "jswrapper.h"
16
17 #include "vm/GlobalObject.h"
18 #include "vm/WeakMapObject.h"
19
20 #include "jsobjinlines.h"
21
22 using namespace js;
23
24 WeakMapBase::WeakMapBase(JSObject *memOf, JSCompartment *c)
25 : memberOf(memOf),
26 compartment(c),
27 next(WeakMapNotInList)
28 {
29 JS_ASSERT_IF(memberOf, memberOf->compartment() == c);
30 }
31
32 WeakMapBase::~WeakMapBase()
33 {
34 JS_ASSERT(next == WeakMapNotInList);
35 }
36
37 bool
38 WeakMapBase::markCompartmentIteratively(JSCompartment *c, JSTracer *tracer)
39 {
40 bool markedAny = false;
41 for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next) {
42 if (m->markIteratively(tracer))
43 markedAny = true;
44 }
45 return markedAny;
46 }
47
48 void
49 WeakMapBase::sweepCompartment(JSCompartment *c)
50 {
51 for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next)
52 m->sweep();
53 }
54
55 void
56 WeakMapBase::traceAllMappings(WeakMapTracer *tracer)
57 {
58 JSRuntime *rt = tracer->runtime;
59 for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
60 for (WeakMapBase *m = c->gcWeakMapList; m; m = m->next)
61 m->traceMappings(tracer);
62 }
63 }
64
65 void
66 WeakMapBase::resetCompartmentWeakMapList(JSCompartment *c)
67 {
68 JS_ASSERT(WeakMapNotInList != nullptr);
69
70 WeakMapBase *m = c->gcWeakMapList;
71 c->gcWeakMapList = nullptr;
72 while (m) {
73 WeakMapBase *n = m->next;
74 m->next = WeakMapNotInList;
75 m = n;
76 }
77 }
78
79 bool
80 WeakMapBase::saveCompartmentWeakMapList(JSCompartment *c, WeakMapVector &vector)
81 {
82 WeakMapBase *m = c->gcWeakMapList;
83 while (m) {
84 if (!vector.append(m))
85 return false;
86 m = m->next;
87 }
88 return true;
89 }
90
91 void
92 WeakMapBase::restoreCompartmentWeakMapLists(WeakMapVector &vector)
93 {
94 for (WeakMapBase **p = vector.begin(); p != vector.end(); p++) {
95 WeakMapBase *m = *p;
96 JS_ASSERT(m->next == WeakMapNotInList);
97 JSCompartment *c = m->compartment;
98 m->next = c->gcWeakMapList;
99 c->gcWeakMapList = m;
100 }
101 }
102
103 void
104 WeakMapBase::removeWeakMapFromList(WeakMapBase *weakmap)
105 {
106 JSCompartment *c = weakmap->compartment;
107 for (WeakMapBase **p = &c->gcWeakMapList; *p; p = &(*p)->next) {
108 if (*p == weakmap) {
109 *p = (*p)->next;
110 weakmap->next = WeakMapNotInList;
111 break;
112 }
113 }
114 }
115
116 static JSObject *
117 GetKeyArg(JSContext *cx, CallArgs &args)
118 {
119 if (args[0].isPrimitive()) {
120 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT);
121 return nullptr;
122 }
123 return &args[0].toObject();
124 }
125
126 MOZ_ALWAYS_INLINE bool
127 IsWeakMap(HandleValue v)
128 {
129 return v.isObject() && v.toObject().is<WeakMapObject>();
130 }
131
132 MOZ_ALWAYS_INLINE bool
133 WeakMap_has_impl(JSContext *cx, CallArgs args)
134 {
135 JS_ASSERT(IsWeakMap(args.thisv()));
136
137 if (args.length() < 1) {
138 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
139 "WeakMap.has", "0", "s");
140 return false;
141 }
142 JSObject *key = GetKeyArg(cx, args);
143 if (!key)
144 return false;
145
146 if (ObjectValueMap *map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
147 if (map->has(key)) {
148 args.rval().setBoolean(true);
149 return true;
150 }
151 }
152
153 args.rval().setBoolean(false);
154 return true;
155 }
156
157 static bool
158 WeakMap_has(JSContext *cx, unsigned argc, Value *vp)
159 {
160 CallArgs args = CallArgsFromVp(argc, vp);
161 return CallNonGenericMethod<IsWeakMap, WeakMap_has_impl>(cx, args);
162 }
163
164 MOZ_ALWAYS_INLINE bool
165 WeakMap_clear_impl(JSContext *cx, CallArgs args)
166 {
167 JS_ASSERT(IsWeakMap(args.thisv()));
168
169 // We can't js_delete the weakmap because the data gathered during GC
170 // is used by the Cycle Collector
171 if (ObjectValueMap *map = args.thisv().toObject().as<WeakMapObject>().getMap())
172 map->clear();
173
174 args.rval().setUndefined();
175 return true;
176 }
177
178 static bool
179 WeakMap_clear(JSContext *cx, unsigned argc, Value *vp)
180 {
181 CallArgs args = CallArgsFromVp(argc, vp);
182 return CallNonGenericMethod<IsWeakMap, WeakMap_clear_impl>(cx, args);
183 }
184
185 MOZ_ALWAYS_INLINE bool
186 WeakMap_get_impl(JSContext *cx, CallArgs args)
187 {
188 JS_ASSERT(IsWeakMap(args.thisv()));
189
190 if (args.length() < 1) {
191 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
192 "WeakMap.get", "0", "s");
193 return false;
194 }
195 JSObject *key = GetKeyArg(cx, args);
196 if (!key)
197 return false;
198
199 if (ObjectValueMap *map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
200 if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
201 // Read barrier to prevent an incorrectly gray value from escaping the
202 // weak map. See the comment before UnmarkGrayChildren in gc/Marking.cpp
203 ExposeValueToActiveJS(ptr->value().get());
204
205 args.rval().set(ptr->value());
206 return true;
207 }
208 }
209
210 args.rval().set((args.length() > 1) ? args[1] : UndefinedValue());
211 return true;
212 }
213
214 static bool
215 WeakMap_get(JSContext *cx, unsigned argc, Value *vp)
216 {
217 CallArgs args = CallArgsFromVp(argc, vp);
218 return CallNonGenericMethod<IsWeakMap, WeakMap_get_impl>(cx, args);
219 }
220
221 MOZ_ALWAYS_INLINE bool
222 WeakMap_delete_impl(JSContext *cx, CallArgs args)
223 {
224 JS_ASSERT(IsWeakMap(args.thisv()));
225
226 if (args.length() < 1) {
227 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
228 "WeakMap.delete", "0", "s");
229 return false;
230 }
231 JSObject *key = GetKeyArg(cx, args);
232 if (!key)
233 return false;
234
235 if (ObjectValueMap *map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
236 if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
237 map->remove(ptr);
238 args.rval().setBoolean(true);
239 return true;
240 }
241 }
242
243 args.rval().setBoolean(false);
244 return true;
245 }
246
247 static bool
248 WeakMap_delete(JSContext *cx, unsigned argc, Value *vp)
249 {
250 CallArgs args = CallArgsFromVp(argc, vp);
251 return CallNonGenericMethod<IsWeakMap, WeakMap_delete_impl>(cx, args);
252 }
253
254 static bool
255 TryPreserveReflector(JSContext *cx, HandleObject obj)
256 {
257 if (obj->getClass()->ext.isWrappedNative ||
258 (obj->getClass()->flags & JSCLASS_IS_DOMJSCLASS) ||
259 (obj->is<ProxyObject>() &&
260 obj->as<ProxyObject>().handler()->family() == GetDOMProxyHandlerFamily()))
261 {
262 JS_ASSERT(cx->runtime()->preserveWrapperCallback);
263 if (!cx->runtime()->preserveWrapperCallback(cx, obj)) {
264 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_WEAKMAP_KEY);
265 return false;
266 }
267 }
268 return true;
269 }
270
271 static inline void
272 WeakMapPostWriteBarrier(JSRuntime *rt, ObjectValueMap *weakMap, JSObject *key)
273 {
274 #ifdef JSGC_GENERATIONAL
275 /*
276 * Strip the barriers from the type before inserting into the store buffer.
277 * This will automatically ensure that barriers do not fire during GC.
278 *
279 * Some compilers complain about instantiating the WeakMap class for
280 * unbarriered type arguments, so we cast to a HashMap instead. Because of
281 * WeakMap's multiple inheritace, We need to do this in two stages, first to
282 * the HashMap base class and then to the unbarriered version.
283 */
284 ObjectValueMap::Base *baseHashMap = static_cast<ObjectValueMap::Base *>(weakMap);
285
286 typedef HashMap<JSObject *, Value> UnbarrieredMap;
287 UnbarrieredMap *unbarrieredMap = reinterpret_cast<UnbarrieredMap *>(baseHashMap);
288
289 typedef gc::HashKeyRef<UnbarrieredMap, JSObject *> Ref;
290 if (key && IsInsideNursery(rt, key))
291 rt->gcStoreBuffer.putGeneric(Ref((unbarrieredMap), key));
292 #endif
293 }
294
295 MOZ_ALWAYS_INLINE bool
296 SetWeakMapEntryInternal(JSContext *cx, Handle<WeakMapObject*> mapObj,
297 HandleObject key, HandleValue value)
298 {
299 ObjectValueMap *map = mapObj->getMap();
300 if (!map) {
301 map = cx->new_<ObjectValueMap>(cx, mapObj.get());
302 if (!map)
303 return false;
304 if (!map->init()) {
305 js_delete(map);
306 JS_ReportOutOfMemory(cx);
307 return false;
308 }
309 mapObj->setPrivate(map);
310 }
311
312 // Preserve wrapped native keys to prevent wrapper optimization.
313 if (!TryPreserveReflector(cx, key))
314 return false;
315
316 if (JSWeakmapKeyDelegateOp op = key->getClass()->ext.weakmapKeyDelegateOp) {
317 RootedObject delegate(cx, op(key));
318 if (delegate && !TryPreserveReflector(cx, delegate))
319 return false;
320 }
321
322 JS_ASSERT(key->compartment() == mapObj->compartment());
323 JS_ASSERT_IF(value.isObject(), value.toObject().compartment() == mapObj->compartment());
324 if (!map->put(key, value)) {
325 JS_ReportOutOfMemory(cx);
326 return false;
327 }
328 WeakMapPostWriteBarrier(cx->runtime(), map, key.get());
329 return true;
330 }
331
332 MOZ_ALWAYS_INLINE bool
333 WeakMap_set_impl(JSContext *cx, CallArgs args)
334 {
335 JS_ASSERT(IsWeakMap(args.thisv()));
336
337 if (args.length() < 1) {
338 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
339 "WeakMap.set", "0", "s");
340 return false;
341 }
342 RootedObject key(cx, GetKeyArg(cx, args));
343 if (!key)
344 return false;
345
346 RootedValue value(cx, (args.length() > 1) ? args[1] : UndefinedValue());
347 Rooted<JSObject*> thisObj(cx, &args.thisv().toObject());
348 Rooted<WeakMapObject*> map(cx, &thisObj->as<WeakMapObject>());
349
350 args.rval().setUndefined();
351 return SetWeakMapEntryInternal(cx, map, key, value);
352 }
353
354 static bool
355 WeakMap_set(JSContext *cx, unsigned argc, Value *vp)
356 {
357 CallArgs args = CallArgsFromVp(argc, vp);
358 return CallNonGenericMethod<IsWeakMap, WeakMap_set_impl>(cx, args);
359 }
360
361 JS_FRIEND_API(bool)
362 JS_NondeterministicGetWeakMapKeys(JSContext *cx, HandleObject objArg, MutableHandleObject ret)
363 {
364 RootedObject obj(cx, objArg);
365 obj = UncheckedUnwrap(obj);
366 if (!obj || !obj->is<WeakMapObject>()) {
367 ret.set(nullptr);
368 return true;
369 }
370 RootedObject arr(cx, NewDenseEmptyArray(cx));
371 if (!arr)
372 return false;
373 ObjectValueMap *map = obj->as<WeakMapObject>().getMap();
374 if (map) {
375 // Prevent GC from mutating the weakmap while iterating.
376 gc::AutoSuppressGC suppress(cx);
377 for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) {
378 RootedObject key(cx, r.front().key());
379 if (!cx->compartment()->wrap(cx, &key))
380 return false;
381 if (!NewbornArrayPush(cx, arr, ObjectValue(*key)))
382 return false;
383 }
384 }
385 ret.set(arr);
386 return true;
387 }
388
389 static void
390 WeakMap_mark(JSTracer *trc, JSObject *obj)
391 {
392 if (ObjectValueMap *map = obj->as<WeakMapObject>().getMap())
393 map->trace(trc);
394 }
395
396 static void
397 WeakMap_finalize(FreeOp *fop, JSObject *obj)
398 {
399 if (ObjectValueMap *map = obj->as<WeakMapObject>().getMap()) {
400 map->check();
401 #ifdef DEBUG
402 map->~ObjectValueMap();
403 memset(static_cast<void *>(map), 0xdc, sizeof(*map));
404 fop->free_(map);
405 #else
406 fop->delete_(map);
407 #endif
408 }
409 }
410
411 JS_PUBLIC_API(JSObject*)
412 JS::NewWeakMapObject(JSContext *cx)
413 {
414 return NewBuiltinClassInstance(cx, &WeakMapObject::class_);
415 }
416
417 JS_PUBLIC_API(bool)
418 JS::IsWeakMapObject(JSObject *obj)
419 {
420 return obj->is<WeakMapObject>();
421 }
422
423 JS_PUBLIC_API(bool)
424 JS::GetWeakMapEntry(JSContext *cx, HandleObject mapObj, HandleObject key,
425 MutableHandleValue rval)
426 {
427 CHECK_REQUEST(cx);
428 assertSameCompartment(cx, key);
429 rval.setUndefined();
430 ObjectValueMap *map = mapObj->as<WeakMapObject>().getMap();
431 if (!map)
432 return true;
433 if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
434 // Read barrier to prevent an incorrectly gray value from escaping the
435 // weak map. See the comment before UnmarkGrayChildren in gc/Marking.cpp
436 ExposeValueToActiveJS(ptr->value().get());
437 rval.set(ptr->value());
438 }
439 return true;
440 }
441
442 JS_PUBLIC_API(bool)
443 JS::SetWeakMapEntry(JSContext *cx, HandleObject mapObj, HandleObject key,
444 HandleValue val)
445 {
446 CHECK_REQUEST(cx);
447 assertSameCompartment(cx, key, val);
448 Rooted<WeakMapObject*> rootedMap(cx, &mapObj->as<WeakMapObject>());
449 return SetWeakMapEntryInternal(cx, rootedMap, key, val);
450 }
451
452 static bool
453 WeakMap_construct(JSContext *cx, unsigned argc, Value *vp)
454 {
455 CallArgs args = CallArgsFromVp(argc, vp);
456 JSObject *obj = NewBuiltinClassInstance(cx, &WeakMapObject::class_);
457 if (!obj)
458 return false;
459
460 args.rval().setObject(*obj);
461 return true;
462 }
463
464 const Class WeakMapObject::class_ = {
465 "WeakMap",
466 JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
467 JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap),
468 JS_PropertyStub, /* addProperty */
469 JS_DeletePropertyStub, /* delProperty */
470 JS_PropertyStub, /* getProperty */
471 JS_StrictPropertyStub, /* setProperty */
472 JS_EnumerateStub,
473 JS_ResolveStub,
474 JS_ConvertStub,
475 WeakMap_finalize,
476 nullptr, /* call */
477 nullptr, /* construct */
478 nullptr, /* xdrObject */
479 WeakMap_mark
480 };
481
482 static const JSFunctionSpec weak_map_methods[] = {
483 JS_FN("has", WeakMap_has, 1, 0),
484 JS_FN("get", WeakMap_get, 2, 0),
485 JS_FN("delete", WeakMap_delete, 1, 0),
486 JS_FN("set", WeakMap_set, 2, 0),
487 JS_FN("clear", WeakMap_clear, 0, 0),
488 JS_FS_END
489 };
490
491 JSObject *
492 js_InitWeakMapClass(JSContext *cx, HandleObject obj)
493 {
494 JS_ASSERT(obj->isNative());
495
496 Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
497
498 RootedObject weakMapProto(cx, global->createBlankPrototype(cx, &WeakMapObject::class_));
499 if (!weakMapProto)
500 return nullptr;
501
502 RootedFunction ctor(cx, global->createConstructor(cx, WeakMap_construct,
503 cx->names().WeakMap, 0));
504 if (!ctor)
505 return nullptr;
506
507 if (!LinkConstructorAndPrototype(cx, ctor, weakMapProto))
508 return nullptr;
509
510 if (!DefinePropertiesAndBrand(cx, weakMapProto, nullptr, weak_map_methods))
511 return nullptr;
512
513 if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakMap, ctor, weakMapProto))
514 return nullptr;
515 return weakMapProto;
516 }
517

mercurial