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 "builtin/WeakMapObject.h"
8 :
9 : #include "jsapi.h"
10 : #include "jscntxt.h"
11 :
12 : #include "vm/SelfHosting.h"
13 :
14 : #include "vm/Interpreter-inl.h"
15 :
16 : using namespace js;
17 : using namespace js::gc;
18 :
19 : MOZ_ALWAYS_INLINE bool
20 318 : IsWeakMap(HandleValue v)
21 : {
22 318 : return v.isObject() && v.toObject().is<WeakMapObject>();
23 : }
24 :
25 : MOZ_ALWAYS_INLINE bool
26 39 : WeakMap_has_impl(JSContext* cx, const CallArgs& args)
27 : {
28 39 : MOZ_ASSERT(IsWeakMap(args.thisv()));
29 :
30 39 : if (!args.get(0).isObject()) {
31 0 : args.rval().setBoolean(false);
32 0 : return true;
33 : }
34 :
35 39 : if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
36 34 : JSObject* key = &args[0].toObject();
37 34 : if (map->has(key)) {
38 27 : args.rval().setBoolean(true);
39 27 : return true;
40 : }
41 : }
42 :
43 12 : args.rval().setBoolean(false);
44 12 : return true;
45 : }
46 :
47 : bool
48 39 : js::WeakMap_has(JSContext* cx, unsigned argc, Value* vp)
49 : {
50 39 : CallArgs args = CallArgsFromVp(argc, vp);
51 39 : return CallNonGenericMethod<IsWeakMap, WeakMap_has_impl>(cx, args);
52 : }
53 :
54 : MOZ_ALWAYS_INLINE bool
55 82 : WeakMap_get_impl(JSContext* cx, const CallArgs& args)
56 : {
57 82 : MOZ_ASSERT(IsWeakMap(args.thisv()));
58 :
59 82 : if (!args.get(0).isObject()) {
60 0 : args.rval().setUndefined();
61 0 : return true;
62 : }
63 :
64 82 : if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
65 68 : JSObject* key = &args[0].toObject();
66 68 : if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
67 62 : args.rval().set(ptr->value());
68 62 : return true;
69 : }
70 : }
71 :
72 20 : args.rval().setUndefined();
73 20 : return true;
74 : }
75 :
76 : bool
77 82 : js::WeakMap_get(JSContext* cx, unsigned argc, Value* vp)
78 : {
79 82 : CallArgs args = CallArgsFromVp(argc, vp);
80 82 : return CallNonGenericMethod<IsWeakMap, WeakMap_get_impl>(cx, args);
81 : }
82 :
83 : MOZ_ALWAYS_INLINE bool
84 9 : WeakMap_delete_impl(JSContext* cx, const CallArgs& args)
85 : {
86 9 : MOZ_ASSERT(IsWeakMap(args.thisv()));
87 :
88 9 : if (!args.get(0).isObject()) {
89 0 : args.rval().setBoolean(false);
90 0 : return true;
91 : }
92 :
93 9 : if (ObjectValueMap* map = args.thisv().toObject().as<WeakMapObject>().getMap()) {
94 9 : JSObject* key = &args[0].toObject();
95 9 : if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
96 9 : map->remove(ptr);
97 9 : args.rval().setBoolean(true);
98 9 : return true;
99 : }
100 : }
101 :
102 0 : args.rval().setBoolean(false);
103 0 : return true;
104 : }
105 :
106 : bool
107 9 : js::WeakMap_delete(JSContext* cx, unsigned argc, Value* vp)
108 : {
109 9 : CallArgs args = CallArgsFromVp(argc, vp);
110 9 : return CallNonGenericMethod<IsWeakMap, WeakMap_delete_impl>(cx, args);
111 : }
112 :
113 : static bool
114 66 : TryPreserveReflector(JSContext* cx, HandleObject obj)
115 : {
116 198 : if (obj->getClass()->isWrappedNative() ||
117 132 : obj->getClass()->isDOMClass() ||
118 69 : (obj->is<ProxyObject>() &&
119 16 : obj->as<ProxyObject>().handler()->family() == GetDOMProxyHandlerFamily()))
120 : {
121 13 : MOZ_ASSERT(cx->runtime()->preserveWrapperCallback);
122 13 : if (!cx->runtime()->preserveWrapperCallback(cx, obj)) {
123 0 : JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_WEAKMAP_KEY);
124 0 : return false;
125 : }
126 : }
127 66 : return true;
128 : }
129 :
130 : static MOZ_ALWAYS_INLINE bool
131 54 : SetWeakMapEntryInternal(JSContext* cx, Handle<WeakMapObject*> mapObj,
132 : HandleObject key, HandleValue value)
133 : {
134 54 : ObjectValueMap* map = mapObj->getMap();
135 54 : if (!map) {
136 34 : auto newMap = cx->make_unique<ObjectValueMap>(cx, mapObj.get());
137 17 : if (!newMap)
138 0 : return false;
139 17 : if (!newMap->init()) {
140 0 : JS_ReportOutOfMemory(cx);
141 0 : return false;
142 : }
143 17 : map = newMap.release();
144 17 : mapObj->setPrivate(map);
145 : }
146 :
147 : // Preserve wrapped native keys to prevent wrapper optimization.
148 54 : if (!TryPreserveReflector(cx, key))
149 0 : return false;
150 :
151 54 : if (JSWeakmapKeyDelegateOp op = key->getClass()->extWeakmapKeyDelegateOp()) {
152 24 : RootedObject delegate(cx, op(key));
153 12 : if (delegate && !TryPreserveReflector(cx, delegate))
154 0 : return false;
155 : }
156 :
157 54 : MOZ_ASSERT(key->compartment() == mapObj->compartment());
158 54 : MOZ_ASSERT_IF(value.isObject(), value.toObject().compartment() == mapObj->compartment());
159 54 : if (!map->put(key, value)) {
160 0 : JS_ReportOutOfMemory(cx);
161 0 : return false;
162 : }
163 54 : return true;
164 : }
165 :
166 : MOZ_ALWAYS_INLINE bool
167 29 : WeakMap_set_impl(JSContext* cx, const CallArgs& args)
168 : {
169 29 : MOZ_ASSERT(IsWeakMap(args.thisv()));
170 :
171 29 : if (!args.get(0).isObject()) {
172 0 : ReportNotObjectWithName(cx, "WeakMap key", args.get(0));
173 0 : return false;
174 : }
175 :
176 58 : RootedObject key(cx, &args[0].toObject());
177 58 : Rooted<JSObject*> thisObj(cx, &args.thisv().toObject());
178 58 : Rooted<WeakMapObject*> map(cx, &thisObj->as<WeakMapObject>());
179 :
180 29 : if (!SetWeakMapEntryInternal(cx, map, key, args.get(1)))
181 0 : return false;
182 29 : args.rval().set(args.thisv());
183 29 : return true;
184 : }
185 :
186 : bool
187 29 : js::WeakMap_set(JSContext* cx, unsigned argc, Value* vp)
188 : {
189 29 : CallArgs args = CallArgsFromVp(argc, vp);
190 29 : return CallNonGenericMethod<IsWeakMap, WeakMap_set_impl>(cx, args);
191 : }
192 :
193 : JS_FRIEND_API(bool)
194 0 : JS_NondeterministicGetWeakMapKeys(JSContext* cx, HandleObject objArg, MutableHandleObject ret)
195 : {
196 0 : RootedObject obj(cx, objArg);
197 0 : obj = UncheckedUnwrap(obj);
198 0 : if (!obj || !obj->is<WeakMapObject>()) {
199 0 : ret.set(nullptr);
200 0 : return true;
201 : }
202 0 : RootedObject arr(cx, NewDenseEmptyArray(cx));
203 0 : if (!arr)
204 0 : return false;
205 0 : ObjectValueMap* map = obj->as<WeakMapObject>().getMap();
206 0 : if (map) {
207 : // Prevent GC from mutating the weakmap while iterating.
208 0 : AutoSuppressGC suppress(cx);
209 0 : for (ObjectValueMap::Base::Range r = map->all(); !r.empty(); r.popFront()) {
210 0 : JS::ExposeObjectToActiveJS(r.front().key());
211 0 : RootedObject key(cx, r.front().key());
212 0 : if (!cx->compartment()->wrap(cx, &key))
213 0 : return false;
214 0 : if (!NewbornArrayPush(cx, arr, ObjectValue(*key)))
215 0 : return false;
216 : }
217 : }
218 0 : ret.set(arr);
219 0 : return true;
220 : }
221 :
222 : static void
223 0 : WeakMap_trace(JSTracer* trc, JSObject* obj)
224 : {
225 0 : if (ObjectValueMap* map = obj->as<WeakMapObject>().getMap())
226 0 : map->trace(trc);
227 0 : }
228 :
229 : static void
230 0 : WeakMap_finalize(FreeOp* fop, JSObject* obj)
231 : {
232 0 : MOZ_ASSERT(fop->maybeOnHelperThread());
233 0 : if (ObjectValueMap* map = obj->as<WeakMapObject>().getMap()) {
234 : #ifdef DEBUG
235 0 : map->~ObjectValueMap();
236 0 : memset(static_cast<void*>(map), 0xdc, sizeof(*map));
237 0 : fop->free_(map);
238 : #else
239 : fop->delete_(map);
240 : #endif
241 : }
242 0 : }
243 :
244 : JS_PUBLIC_API(JSObject*)
245 1 : JS::NewWeakMapObject(JSContext* cx)
246 : {
247 1 : return NewBuiltinClassInstance(cx, &WeakMapObject::class_);
248 : }
249 :
250 : JS_PUBLIC_API(bool)
251 522 : JS::IsWeakMapObject(JSObject* obj)
252 : {
253 522 : return obj->is<WeakMapObject>();
254 : }
255 :
256 : JS_PUBLIC_API(bool)
257 523 : JS::GetWeakMapEntry(JSContext* cx, HandleObject mapObj, HandleObject key,
258 : MutableHandleValue rval)
259 : {
260 1046 : CHECK_REQUEST(cx);
261 523 : assertSameCompartment(cx, key);
262 523 : rval.setUndefined();
263 523 : ObjectValueMap* map = mapObj->as<WeakMapObject>().getMap();
264 523 : if (!map)
265 1 : return true;
266 522 : if (ObjectValueMap::Ptr ptr = map->lookup(key)) {
267 : // Read barrier to prevent an incorrectly gray value from escaping the
268 : // weak map. See the comment before UnmarkGrayChildren in gc/Marking.cpp
269 498 : ExposeValueToActiveJS(ptr->value().get());
270 498 : rval.set(ptr->value());
271 : }
272 522 : return true;
273 : }
274 :
275 : JS_PUBLIC_API(bool)
276 25 : JS::SetWeakMapEntry(JSContext* cx, HandleObject mapObj, HandleObject key,
277 : HandleValue val)
278 : {
279 50 : CHECK_REQUEST(cx);
280 25 : assertSameCompartment(cx, key, val);
281 50 : Rooted<WeakMapObject*> rootedMap(cx, &mapObj->as<WeakMapObject>());
282 50 : return SetWeakMapEntryInternal(cx, rootedMap, key, val);
283 : }
284 :
285 : static bool
286 58 : WeakMap_construct(JSContext* cx, unsigned argc, Value* vp)
287 : {
288 58 : CallArgs args = CallArgsFromVp(argc, vp);
289 :
290 : // ES6 draft rev 31 (15 Jan 2015) 23.3.1.1 step 1.
291 58 : if (!ThrowIfNotConstructing(cx, args, "WeakMap"))
292 0 : return false;
293 :
294 116 : RootedObject proto(cx);
295 58 : if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto))
296 0 : return false;
297 :
298 116 : RootedObject obj(cx, NewObjectWithClassProto<WeakMapObject>(cx, proto));
299 58 : if (!obj)
300 0 : return false;
301 :
302 : // Steps 5-6, 11.
303 58 : if (!args.get(0).isNullOrUndefined()) {
304 0 : FixedInvokeArgs<1> args2(cx);
305 0 : args2[0].set(args[0]);
306 :
307 0 : RootedValue thisv(cx, ObjectValue(*obj));
308 0 : if (!CallSelfHostedFunction(cx, cx->names().WeakMapConstructorInit, thisv, args2, args2.rval()))
309 0 : return false;
310 : }
311 :
312 58 : args.rval().setObject(*obj);
313 58 : return true;
314 : }
315 :
316 : static const ClassOps WeakMapObjectClassOps = {
317 : nullptr, /* addProperty */
318 : nullptr, /* delProperty */
319 : nullptr, /* getProperty */
320 : nullptr, /* setProperty */
321 : nullptr, /* enumerate */
322 : nullptr, /* newEnumerate */
323 : nullptr, /* resolve */
324 : nullptr, /* mayResolve */
325 : WeakMap_finalize,
326 : nullptr, /* call */
327 : nullptr, /* hasInstance */
328 : nullptr, /* construct */
329 : WeakMap_trace
330 : };
331 :
332 : const Class WeakMapObject::class_ = {
333 : "WeakMap",
334 : JSCLASS_HAS_PRIVATE |
335 : JSCLASS_HAS_CACHED_PROTO(JSProto_WeakMap) |
336 : JSCLASS_BACKGROUND_FINALIZE,
337 : &WeakMapObjectClassOps
338 : };
339 :
340 : static const JSFunctionSpec weak_map_methods[] = {
341 : JS_FN("has", WeakMap_has, 1, 0),
342 : JS_FN("get", WeakMap_get, 1, 0),
343 : JS_FN("delete", WeakMap_delete, 1, 0),
344 : JS_FN("set", WeakMap_set, 2, 0),
345 : JS_FS_END
346 : };
347 :
348 : static JSObject*
349 38 : InitWeakMapClass(JSContext* cx, HandleObject obj, bool defineMembers)
350 : {
351 38 : MOZ_ASSERT(obj->isNative());
352 :
353 38 : Handle<GlobalObject*> global = obj.as<GlobalObject>();
354 :
355 76 : RootedPlainObject proto(cx, NewBuiltinClassInstance<PlainObject>(cx));
356 38 : if (!proto)
357 0 : return nullptr;
358 :
359 76 : RootedFunction ctor(cx, GlobalObject::createConstructor(cx, WeakMap_construct,
360 114 : cx->names().WeakMap, 0));
361 38 : if (!ctor)
362 0 : return nullptr;
363 :
364 38 : if (!LinkConstructorAndPrototype(cx, ctor, proto))
365 0 : return nullptr;
366 :
367 38 : if (defineMembers) {
368 35 : if (!DefinePropertiesAndFunctions(cx, proto, nullptr, weak_map_methods))
369 0 : return nullptr;
370 35 : if (!DefineToStringTag(cx, proto, cx->names().WeakMap))
371 0 : return nullptr;
372 : }
373 :
374 38 : if (!GlobalObject::initBuiltinConstructor(cx, global, JSProto_WeakMap, ctor, proto))
375 0 : return nullptr;
376 38 : return proto;
377 : }
378 :
379 : JSObject*
380 35 : js::InitWeakMapClass(JSContext* cx, HandleObject obj)
381 : {
382 35 : return InitWeakMapClass(cx, obj, true);
383 : }
384 :
385 : JSObject*
386 3 : js::InitBareWeakMapCtor(JSContext* cx, HandleObject obj)
387 : {
388 3 : return InitWeakMapClass(cx, obj, false);
389 : }
390 :
|