1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : * vim: set ts=8 sw=4 et tw=99 ft=cpp:
3 : *
4 : * ***** BEGIN LICENSE BLOCK *****
5 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 : *
7 : * The contents of this file are subject to the Mozilla Public License Version
8 : * 1.1 (the "License"); you may not use this file except in compliance with
9 : * the License. You may obtain a copy of the License at
10 : * http://www.mozilla.org/MPL/
11 : *
12 : * Software distributed under the License is distributed on an "AS IS" basis,
13 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 : * for the specific language governing rights and limitations under the
15 : * License.
16 : *
17 : * The Original Code is SpiderMonkey JavaScript engine.
18 : *
19 : * The Initial Developer of the Original Code is
20 : * the Mozilla Foundation.
21 : * Portions created by the Initial Developer are Copyright (C) 2011-2012
22 : * the Initial Developer. All Rights Reserved.
23 : *
24 : * Contributor(s):
25 : * Jason Orendorff <jorendorff@mozilla.com>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either the GNU General Public License Version 2 or later (the "GPL"), or
29 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include "builtin/MapObject.h"
42 :
43 : #include "jscntxt.h"
44 : #include "jsgcmark.h"
45 : #include "jsiter.h"
46 : #include "jsobj.h"
47 :
48 : #include "vm/GlobalObject.h"
49 : #include "vm/MethodGuard.h"
50 : #include "vm/Stack.h"
51 :
52 : #include "jsobjinlines.h"
53 :
54 : using namespace js;
55 :
56 : static JSObject *
57 1885 : InitClass(JSContext *cx, GlobalObject *global, Class *clasp, JSProtoKey key, Native construct,
58 : JSFunctionSpec *methods)
59 : {
60 1885 : JSObject *proto = global->createBlankPrototype(cx, clasp);
61 1885 : if (!proto)
62 0 : return NULL;
63 1885 : proto->setPrivate(NULL);
64 :
65 1885 : JSAtom *atom = cx->runtime->atomState.classAtoms[key];
66 1885 : JSFunction *ctor = global->createConstructor(cx, construct, clasp, atom, 1);
67 7540 : if (!ctor ||
68 1885 : !LinkConstructorAndPrototype(cx, ctor, proto) ||
69 1885 : !DefinePropertiesAndBrand(cx, proto, NULL, methods) ||
70 1885 : !DefineConstructorAndPrototype(cx, global, key, ctor, proto))
71 : {
72 0 : return NULL;
73 : }
74 1885 : return proto;
75 : }
76 :
77 :
78 : /*** HashableValue *******************************************************************************/
79 :
80 : bool
81 2369286 : HashableValue::setValue(JSContext *cx, const Value &v)
82 : {
83 2369286 : if (v.isString() && v.toString()->isRope()) {
84 : /* Flatten this rope so that equals() is infallible. */
85 378 : JSString *str = v.toString()->ensureLinear(cx);
86 378 : if (!str)
87 0 : return false;
88 378 : value = StringValue(str);
89 2368908 : } else if (v.isDouble()) {
90 1683 : double d = v.toDouble();
91 : int32_t i;
92 1683 : if (JSDOUBLE_IS_INT32(d, &i)) {
93 : /* Normalize int32-valued doubles to int32 for faster hashing and testing. */
94 108 : value = Int32Value(i);
95 1575 : } else if (JSDOUBLE_IS_NaN(d)) {
96 : /* NaNs with different bits must hash and test identically. */
97 225 : value = DoubleValue(js_NaN);
98 : } else {
99 1350 : value = v;
100 : }
101 : } else {
102 2367225 : value = v;
103 : }
104 :
105 9483480 : JS_ASSERT(value.isUndefined() || value.isNull() || value.isBoolean() ||
106 9483480 : value.isNumber() || value.isString() || value.isObject());
107 2369286 : return true;
108 : }
109 :
110 : HashNumber
111 2369457 : HashableValue::hash() const
112 : {
113 : /*
114 : * HashableValue::setValue normalizes values so that the SameValue relation
115 : * on HashableValues is the same as the == relationship on
116 : * value.data.asBits, except for strings.
117 : */
118 2369457 : if (value.isString()) {
119 3906 : JSLinearString &s = value.toString()->asLinear();
120 3906 : return HashChars(s.chars(), s.length());
121 : }
122 :
123 : /* Having dispensed with strings, we can just hash asBits. */
124 2365551 : uint64_t u = value.asRawBits();
125 2365551 : return HashNumber((u >> 3) ^ (u >> (32 + 3)) ^ (u << (32 - 3)));
126 : }
127 :
128 : bool
129 1183797 : HashableValue::equals(const HashableValue &other) const
130 : {
131 : /* Two HashableValues are equal if they have equal bits or they're equal strings. */
132 1183797 : bool b = (value.asRawBits() == other.value.asRawBits()) ||
133 1521 : (value.isString() &&
134 1431 : other.value.isString() &&
135 1431 : EqualStrings(&value.toString()->asLinear(),
136 1188180 : &other.value.toString()->asLinear()));
137 :
138 : #ifdef DEBUG
139 : bool same;
140 1183797 : JS_ASSERT(SameValue(NULL, value, other.value, &same));
141 1183797 : JS_ASSERT(same == b);
142 : #endif
143 1183797 : return b;
144 : }
145 :
146 :
147 : /*** Map *****************************************************************************************/
148 :
149 : Class MapObject::class_ = {
150 : "Map",
151 : JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
152 : JSCLASS_HAS_CACHED_PROTO(JSProto_Map),
153 : JS_PropertyStub, /* addProperty */
154 : JS_PropertyStub, /* delProperty */
155 : JS_PropertyStub, /* getProperty */
156 : JS_StrictPropertyStub, /* setProperty */
157 : JS_EnumerateStub,
158 : JS_ResolveStub,
159 : JS_ConvertStub,
160 : finalize,
161 : NULL, /* checkAccess */
162 : NULL, /* call */
163 : NULL, /* construct */
164 : NULL, /* hasInstance */
165 : mark
166 : };
167 :
168 : JSFunctionSpec MapObject::methods[] = {
169 : JS_FN("size", size, 0, 0),
170 : JS_FN("get", get, 1, 0),
171 : JS_FN("has", has, 1, 0),
172 : JS_FN("set", set, 2, 0),
173 : JS_FN("delete", delete_, 1, 0),
174 : JS_FS_END
175 : };
176 :
177 : JSObject *
178 983 : MapObject::initClass(JSContext *cx, JSObject *obj)
179 : {
180 983 : return InitClass(cx, &obj->asGlobal(), &class_, JSProto_Map, construct, methods);
181 : }
182 :
183 : void
184 7748 : MapObject::mark(JSTracer *trc, JSObject *obj)
185 : {
186 7748 : MapObject *mapobj = static_cast<MapObject *>(obj);
187 7748 : if (ValueMap *map = mapobj->getData()) {
188 9396 : for (ValueMap::Range r = map->all(); !r.empty(); r.popFront()) {
189 4500 : const HeapValue &key = r.front().key;
190 9000 : HeapValue tmp(key);
191 4500 : gc::MarkValue(trc, &tmp, "key");
192 4500 : JS_ASSERT(tmp.get() == key.get());
193 4500 : gc::MarkValue(trc, &r.front().value, "value");
194 : }
195 : }
196 7748 : }
197 :
198 : void
199 1864 : MapObject::finalize(JSContext *cx, JSObject *obj)
200 : {
201 1864 : MapObject *mapobj = static_cast<MapObject *>(obj);
202 1864 : if (ValueMap *map = mapobj->getData())
203 882 : cx->delete_(map);
204 1864 : }
205 :
206 : class AddToMap {
207 : private:
208 : ValueMap *map;
209 :
210 : public:
211 207 : AddToMap(ValueMap *map) : map(map) {}
212 :
213 684 : bool operator()(JSContext *cx, const Value &v) {
214 684 : JSObject *pairobj = js_ValueToNonNullObject(cx, v);
215 684 : if (!pairobj)
216 36 : return false;
217 :
218 : Value key;
219 648 : if (!pairobj->getElement(cx, 0, &key))
220 0 : return false;
221 1296 : HashableValue hkey;
222 648 : if (!hkey.setValue(cx, key))
223 0 : return false;
224 :
225 : Value val;
226 648 : if (!pairobj->getElement(cx, 1, &val))
227 0 : return false;
228 :
229 648 : if (!map->put(hkey, val)) {
230 0 : js_ReportOutOfMemory(cx);
231 0 : return false;
232 : }
233 648 : return true;
234 : }
235 : };
236 :
237 : JSBool
238 882 : MapObject::construct(JSContext *cx, unsigned argc, Value *vp)
239 : {
240 882 : JSObject *obj = NewBuiltinClassInstance(cx, &class_);
241 882 : if (!obj)
242 0 : return false;
243 :
244 882 : ValueMap *map = cx->new_<ValueMap>(cx->runtime);
245 882 : if (!map)
246 0 : return false;
247 882 : if (!map->init()) {
248 0 : js_ReportOutOfMemory(cx);
249 0 : return false;
250 : }
251 882 : obj->setPrivate(map);
252 :
253 882 : CallArgs args = CallArgsFromVp(argc, vp);
254 882 : if (args.hasDefined(0)) {
255 207 : if (!ForOf(cx, args[0], AddToMap(map)))
256 144 : return false;
257 : }
258 :
259 738 : args.rval().setObject(*obj);
260 738 : return true;
261 : }
262 :
263 : #define UNPACK_THIS(T, native, cx, argc, vp, args, data) \
264 : CallArgs args = CallArgsFromVp(argc, vp); \
265 : if (!args.thisv().isObject() || \
266 : !args.thisv().toObject().hasClass(&T::class_)) \
267 : { \
268 : return js::HandleNonGenericMethodClassMismatch(cx, args, native, \
269 : &T::class_); \
270 : } \
271 : if (!args.thisv().toObject().getPrivate()) { \
272 : ReportIncompatibleMethod(cx, args, &T::class_); \
273 : return false; \
274 : } \
275 : T::Data &data = *static_cast<T &>(args.thisv().toObject()).getData(); \
276 : (void) data
277 :
278 : #define THIS_MAP(native, cx, argc, vp, args, map) \
279 : UNPACK_THIS(MapObject, native, cx, argc, vp, args, map)
280 :
281 : #define ARG0_KEY(cx, args, key) \
282 : HashableValue key; \
283 : if (args.length() > 0 && !key.setValue(cx, args[0])) \
284 : return false
285 :
286 : JSBool
287 252 : MapObject::size(JSContext *cx, unsigned argc, Value *vp)
288 : {
289 252 : THIS_MAP(get, cx, argc, vp, args, map);
290 : JS_STATIC_ASSERT(sizeof map.count() <= sizeof(uint32_t));
291 198 : args.rval().setNumber(map.count());
292 198 : return true;
293 : }
294 :
295 : JSBool
296 591318 : MapObject::get(JSContext *cx, unsigned argc, Value *vp)
297 : {
298 591318 : THIS_MAP(get, cx, argc, vp, args, map);
299 1182402 : ARG0_KEY(cx, args, key);
300 :
301 591201 : if (ValueMap::Ptr p = map.lookup(key))
302 590445 : args.rval() = p->value;
303 : else
304 756 : args.rval().setUndefined();
305 591201 : return true;
306 : }
307 :
308 : JSBool
309 1683 : MapObject::has(JSContext *cx, unsigned argc, Value *vp)
310 : {
311 1683 : THIS_MAP(has, cx, argc, vp, args, map);
312 3132 : ARG0_KEY(cx, args, key);
313 1566 : args.rval().setBoolean(map.lookup(key));
314 1566 : return true;
315 : }
316 :
317 : JSBool
318 591201 : MapObject::set(JSContext *cx, unsigned argc, Value *vp)
319 : {
320 591201 : THIS_MAP(set, cx, argc, vp, args, map);
321 1182168 : ARG0_KEY(cx, args, key);
322 591084 : if (!map.put(key, args.length() > 1 ? args[1] : UndefinedValue())) {
323 0 : js_ReportOutOfMemory(cx);
324 0 : return false;
325 : }
326 591084 : args.rval().setUndefined();
327 591084 : return true;
328 : }
329 :
330 : JSBool
331 585 : MapObject::delete_(JSContext *cx, unsigned argc, Value *vp)
332 : {
333 585 : THIS_MAP(delete_, cx, argc, vp, args, map);
334 936 : ARG0_KEY(cx, args, key);
335 468 : ValueMap::Ptr p = map.lookup(key);
336 468 : bool found = p.found();
337 468 : if (found)
338 378 : map.remove(p);
339 468 : args.rval().setBoolean(found);
340 468 : return true;
341 : }
342 :
343 : JSObject *
344 983 : js_InitMapClass(JSContext *cx, JSObject *obj)
345 : {
346 983 : return MapObject::initClass(cx, obj);
347 : }
348 :
349 :
350 : /*** Set *****************************************************************************************/
351 :
352 : Class SetObject::class_ = {
353 : "Set",
354 : JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
355 : JSCLASS_HAS_CACHED_PROTO(JSProto_Set),
356 : JS_PropertyStub, /* addProperty */
357 : JS_PropertyStub, /* delProperty */
358 : JS_PropertyStub, /* getProperty */
359 : JS_StrictPropertyStub, /* setProperty */
360 : JS_EnumerateStub,
361 : JS_ResolveStub,
362 : JS_ConvertStub,
363 : finalize,
364 : NULL, /* checkAccess */
365 : NULL, /* call */
366 : NULL, /* construct */
367 : NULL, /* hasInstance */
368 : mark
369 : };
370 :
371 : JSFunctionSpec SetObject::methods[] = {
372 : JS_FN("size", size, 0, 0),
373 : JS_FN("has", has, 1, 0),
374 : JS_FN("add", add, 1, 0),
375 : JS_FN("delete", delete_, 1, 0),
376 : JS_FS_END
377 : };
378 :
379 : JSObject *
380 902 : SetObject::initClass(JSContext *cx, JSObject *obj)
381 : {
382 902 : return InitClass(cx, &obj->asGlobal(), &class_, JSProto_Set, construct, methods);
383 : }
384 :
385 : void
386 5102 : SetObject::mark(JSTracer *trc, JSObject *obj)
387 : {
388 5102 : SetObject *setobj = static_cast<SetObject *>(obj);
389 5102 : if (ValueSet *set = setobj->getData()) {
390 4698 : for (ValueSet::Range r = set->all(); !r.empty(); r.popFront()) {
391 2250 : const HeapValue &key = r.front();
392 4500 : HeapValue tmp(key);
393 2250 : gc::MarkValue(trc, &tmp, "key");
394 2250 : JS_ASSERT(tmp.get() == key.get());
395 : }
396 : }
397 5102 : }
398 :
399 : void
400 1495 : SetObject::finalize(JSContext *cx, JSObject *obj)
401 : {
402 1495 : SetObject *setobj = static_cast<SetObject *>(obj);
403 1495 : if (ValueSet *set = setobj->getData())
404 594 : cx->delete_(set);
405 1495 : }
406 :
407 : class AddToSet {
408 : private:
409 : ValueSet *set;
410 :
411 : public:
412 162 : AddToSet(ValueSet *set) : set(set) {}
413 :
414 3285 : bool operator()(JSContext *cx, const Value &v) {
415 6570 : HashableValue key;
416 3285 : if (!key.setValue(cx, v))
417 0 : return false;
418 3285 : if (!set->put(key)) {
419 0 : js_ReportOutOfMemory(cx);
420 0 : return false;
421 : }
422 3285 : return true;
423 : }
424 : };
425 :
426 : JSBool
427 594 : SetObject::construct(JSContext *cx, unsigned argc, Value *vp)
428 : {
429 594 : JSObject *obj = NewBuiltinClassInstance(cx, &class_);
430 594 : if (!obj)
431 0 : return false;
432 :
433 594 : ValueSet *set = cx->new_<ValueSet>(cx->runtime);
434 594 : if (!set)
435 0 : return false;
436 594 : if (!set->init()) {
437 0 : js_ReportOutOfMemory(cx);
438 0 : return false;
439 : }
440 594 : obj->setPrivate(set);
441 :
442 594 : CallArgs args = CallArgsFromVp(argc, vp);
443 594 : if (args.hasDefined(0)) {
444 162 : if (!ForOf(cx, args[0], AddToSet(set)))
445 0 : return false;
446 : }
447 :
448 594 : args.rval().setObject(*obj);
449 594 : return true;
450 : }
451 :
452 : #define THIS_SET(native, cx, argc, vp, args, set) \
453 : UNPACK_THIS(SetObject, native, cx, argc, vp, args, set)
454 :
455 : JSBool
456 756 : SetObject::size(JSContext *cx, unsigned argc, Value *vp)
457 : {
458 756 : THIS_SET(has, cx, argc, vp, args, set);
459 : JS_STATIC_ASSERT(sizeof set.count() <= sizeof(uint32_t));
460 756 : args.rval().setNumber(set.count());
461 756 : return true;
462 : }
463 :
464 : JSBool
465 590346 : SetObject::has(JSContext *cx, unsigned argc, Value *vp)
466 : {
467 590346 : THIS_SET(has, cx, argc, vp, args, set);
468 1180458 : ARG0_KEY(cx, args, key);
469 590229 : args.rval().setBoolean(set.has(key));
470 590229 : return true;
471 : }
472 :
473 : JSBool
474 590742 : SetObject::add(JSContext *cx, unsigned argc, Value *vp)
475 : {
476 590742 : THIS_SET(add, cx, argc, vp, args, set);
477 1181250 : ARG0_KEY(cx, args, key);
478 590625 : if (!set.put(key)) {
479 0 : js_ReportOutOfMemory(cx);
480 0 : return false;
481 : }
482 590625 : args.rval().setUndefined();
483 590625 : return true;
484 : }
485 :
486 : JSBool
487 468 : SetObject::delete_(JSContext *cx, unsigned argc, Value *vp)
488 : {
489 468 : THIS_SET(delete_, cx, argc, vp, args, set);
490 702 : ARG0_KEY(cx, args, key);
491 351 : ValueSet::Ptr p = set.lookup(key);
492 351 : bool found = p.found();
493 351 : if (found)
494 126 : set.remove(p);
495 351 : args.rval().setBoolean(found);
496 351 : return true;
497 : }
498 :
499 : JSObject *
500 902 : js_InitSetClass(JSContext *cx, JSObject *obj)
501 : {
502 902 : return SetObject::initClass(cx, obj);
503 : }
|