1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sw=4 et tw=78:
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 Mozilla Communicator client code, released
18 * March 31, 1998.
19 *
20 * The Initial Developer of the Original Code is
21 * Netscape Communications Corporation.
22 * Portions created by the Initial Developer are Copyright (C) 1998
23 * the Initial Developer. All Rights Reserved.
24 *
25 * Contributor(s):
26 *
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or 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 ***** */
41 /*
42 * JS object implementation.
43 */
44 #include "jsstddef.h"
45 #include <stdlib.h>
46 #include <string.h>
47 #include "jstypes.h"
48 #include "jsarena.h" /* Added by JSIFY */
49 #include "jsutil.h" /* Added by JSIFY */
50 #include "jshash.h" /* Added by JSIFY */
51 #include "jsdhash.h"
52 #include "jsprf.h"
53 #include "jsapi.h"
54 #include "jsarray.h"
55 #include "jsatom.h"
56 #include "jsbool.h"
57 #include "jscntxt.h"
58 #include "jsconfig.h"
59 #include "jsfun.h"
60 #include "jsgc.h"
61 #include "jsinterp.h"
62 #include "jslock.h"
63 #include "jsnum.h"
64 #include "jsobj.h"
65 #include "jsscope.h"
66 #include "jsscript.h"
67 #include "jsstr.h"
68 #include "jsopcode.h"
70 #include "jsdbgapi.h" /* whether or not JS_HAS_OBJ_WATCHPOINT */
72 #if JS_HAS_XML_SUPPORT
73 #include "jsxml.h"
74 #endif
76 #ifdef JS_THREADSAFE
77 #define NATIVE_DROP_PROPERTY js_DropProperty
79 extern void
80 js_DropProperty(JSContext *cx, JSObject *obj, JSProperty *prop);
81 #else
82 #define NATIVE_DROP_PROPERTY NULL
83 #endif
85 JS_FRIEND_DATA(JSObjectOps) js_ObjectOps = {
86 js_NewObjectMap, js_DestroyObjectMap,
87 js_LookupProperty, js_DefineProperty,
88 js_GetProperty, js_SetProperty,
89 js_GetAttributes, js_SetAttributes,
90 js_DeleteProperty, js_DefaultValue,
91 js_Enumerate, js_CheckAccess,
92 NULL, NATIVE_DROP_PROPERTY,
93 js_Call, js_Construct,
94 NULL, js_HasInstance,
95 js_SetProtoOrParent, js_SetProtoOrParent,
96 js_Mark, js_Clear,
97 js_GetRequiredSlot, js_SetRequiredSlot
98 };
100 JSClass js_ObjectClass = {
101 js_Object_str,
102 0,
103 JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
104 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
105 JSCLASS_NO_OPTIONAL_MEMBERS
106 };
108 #if JS_HAS_OBJ_PROTO_PROP
110 static JSBool
111 obj_getSlot(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
113 static JSBool
114 obj_setSlot(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
116 static JSBool
117 obj_getCount(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
119 static JSPropertySpec object_props[] = {
120 /* These two must come first; see object_props[slot].name usage below. */
121 {js_proto_str, JSSLOT_PROTO, JSPROP_PERMANENT|JSPROP_SHARED,
122 obj_getSlot, obj_setSlot},
123 {js_parent_str,JSSLOT_PARENT,JSPROP_READONLY|JSPROP_PERMANENT|JSPROP_SHARED,
124 obj_getSlot, obj_setSlot},
125 {js_count_str, 0, JSPROP_PERMANENT,obj_getCount, obj_getCount},
126 {0,0,0,0,0}
127 };
129 /* NB: JSSLOT_PROTO and JSSLOT_PARENT are already indexes into object_props. */
130 #define JSSLOT_COUNT 2
132 static JSBool
133 ReportStrictSlot(JSContext *cx, uint32 slot)
134 {
135 if (slot == JSSLOT_PROTO)
136 return JS_TRUE;
137 return JS_ReportErrorFlagsAndNumber(cx,
138 JSREPORT_WARNING | JSREPORT_STRICT,
139 js_GetErrorMessage, NULL,
140 JSMSG_DEPRECATED_USAGE,
141 object_props[slot].name);
142 }
144 static JSBool
145 obj_getSlot(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
146 {
147 uint32 slot;
148 jsid propid;
149 JSAccessMode mode;
150 uintN attrs;
151 JSObject *pobj;
152 JSClass *clasp;
153 JSExtendedClass *xclasp;
155 slot = (uint32) JSVAL_TO_INT(id);
156 if (id == INT_TO_JSVAL(JSSLOT_PROTO)) {
157 propid = ATOM_TO_JSID(cx->runtime->atomState.protoAtom);
158 mode = JSACC_PROTO;
159 } else {
160 propid = ATOM_TO_JSID(cx->runtime->atomState.parentAtom);
161 mode = JSACC_PARENT;
162 }
164 /* Let OBJ_CHECK_ACCESS get the slot's value, based on the access mode. */
165 if (!OBJ_CHECK_ACCESS(cx, obj, propid, mode, vp, &attrs))
166 return JS_FALSE;
168 pobj = JSVAL_TO_OBJECT(*vp);
169 if (pobj) {
170 clasp = OBJ_GET_CLASS(cx, pobj);
171 if (clasp->flags & JSCLASS_IS_EXTENDED) {
172 xclasp = (JSExtendedClass *) clasp;
173 if (xclasp->outerObject) {
174 pobj = xclasp->outerObject(cx, pobj);
175 if (!pobj)
176 return JS_FALSE;
177 *vp = OBJECT_TO_JSVAL(pobj);
178 }
179 }
180 }
181 return JS_TRUE;
182 }
184 static JSBool
185 obj_setSlot(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
186 {
187 JSObject *pobj;
188 uint32 slot;
189 jsid propid;
190 uintN attrs;
192 if (!JSVAL_IS_OBJECT(*vp))
193 return JS_TRUE;
194 pobj = JSVAL_TO_OBJECT(*vp);
195 slot = (uint32) JSVAL_TO_INT(id);
196 if (JS_HAS_STRICT_OPTION(cx) && !ReportStrictSlot(cx, slot))
197 return JS_FALSE;
199 /* __parent__ is readonly and permanent, only __proto__ may be set. */
200 propid = ATOM_TO_JSID(cx->runtime->atomState.protoAtom);
201 if (!OBJ_CHECK_ACCESS(cx, obj, propid, JSACC_PROTO|JSACC_WRITE, vp, &attrs))
202 return JS_FALSE;
204 return js_SetProtoOrParent(cx, obj, slot, pobj);
205 }
207 static JSBool
208 obj_getCount(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
209 {
210 jsval iter_state;
211 jsid num_properties;
212 JSBool ok;
214 if (JS_HAS_STRICT_OPTION(cx) && !ReportStrictSlot(cx, JSSLOT_COUNT))
215 return JS_FALSE;
217 /* Get the number of properties to enumerate. */
218 iter_state = JSVAL_NULL;
219 ok = OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &iter_state, &num_properties);
220 if (!ok)
221 goto out;
223 if (!JSVAL_IS_INT(num_properties)) {
224 JS_ASSERT(0);
225 *vp = JSVAL_ZERO;
226 goto out;
227 }
228 *vp = num_properties;
230 out:
231 if (iter_state != JSVAL_NULL)
232 ok = OBJ_ENUMERATE(cx, obj, JSENUMERATE_DESTROY, &iter_state, 0);
233 return ok;
234 }
236 #else /* !JS_HAS_OBJ_PROTO_PROP */
238 #define object_props NULL
240 #endif /* !JS_HAS_OBJ_PROTO_PROP */
242 JSBool
243 js_SetProtoOrParent(JSContext *cx, JSObject *obj, uint32 slot, JSObject *pobj)
244 {
245 JSRuntime *rt;
246 JSObject *obj2, *oldproto;
247 JSScope *scope, *newscope;
249 /*
250 * Serialize all proto and parent setting in order to detect cycles.
251 * We nest locks in this function, and only here, in the following orders:
252 *
253 * (1) rt->setSlotLock < pobj's scope lock;
254 * rt->setSlotLock < pobj's proto-or-parent's scope lock;
255 * rt->setSlotLock < pobj's grand-proto-or-parent's scope lock;
256 * etc...
257 * (2) rt->setSlotLock < obj's scope lock < pobj's scope lock.
258 *
259 * We avoid AB-BA deadlock by restricting obj from being on pobj's parent
260 * or proto chain (pobj may already be on obj's parent or proto chain; it
261 * could be moving up or down). We finally order obj with respect to pobj
262 * at the bottom of this routine (just before releasing rt->setSlotLock),
263 * by making pobj be obj's prototype or parent.
264 *
265 * After we have set the slot and released rt->setSlotLock, another call
266 * to js_SetProtoOrParent could nest locks according to the first order
267 * list above, but it cannot deadlock with any other thread. For there
268 * to be a deadlock, other parts of the engine would have to nest scope
269 * locks in the opposite order. XXXbe ensure they don't!
270 */
271 rt = cx->runtime;
272 #ifdef JS_THREADSAFE
274 JS_ACQUIRE_LOCK(rt->setSlotLock);
275 while (rt->setSlotBusy) {
276 jsrefcount saveDepth;
278 /* Take pains to avoid nesting rt->gcLock inside rt->setSlotLock! */
279 JS_RELEASE_LOCK(rt->setSlotLock);
280 saveDepth = JS_SuspendRequest(cx);
281 JS_ACQUIRE_LOCK(rt->setSlotLock);
282 if (rt->setSlotBusy)
283 JS_WAIT_CONDVAR(rt->setSlotDone, JS_NO_TIMEOUT);
284 JS_RELEASE_LOCK(rt->setSlotLock);
285 JS_ResumeRequest(cx, saveDepth);
286 JS_ACQUIRE_LOCK(rt->setSlotLock);
287 }
288 rt->setSlotBusy = JS_TRUE;
289 JS_RELEASE_LOCK(rt->setSlotLock);
291 #define SET_SLOT_DONE(rt) \
292 JS_BEGIN_MACRO \
293 JS_ACQUIRE_LOCK((rt)->setSlotLock); \
294 (rt)->setSlotBusy = JS_FALSE; \
295 JS_NOTIFY_ALL_CONDVAR((rt)->setSlotDone); \
296 JS_RELEASE_LOCK((rt)->setSlotLock); \
297 JS_END_MACRO
299 #else
301 #define SET_SLOT_DONE(rt) /* nothing */
303 #endif
305 obj2 = pobj;
306 while (obj2) {
307 if (obj2 == obj) {
308 SET_SLOT_DONE(rt);
309 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
310 JSMSG_CYCLIC_VALUE,
311 #if JS_HAS_OBJ_PROTO_PROP
312 object_props[slot].name
313 #else
314 (slot == JSSLOT_PROTO) ? js_proto_str
315 : js_parent_str
316 #endif
317 );
318 return JS_FALSE;
319 }
320 obj2 = JSVAL_TO_OBJECT(OBJ_GET_SLOT(cx, obj2, slot));
321 }
323 if (slot == JSSLOT_PROTO && OBJ_IS_NATIVE(obj)) {
324 /* Check to see whether obj shares its prototype's scope. */
325 JS_LOCK_OBJ(cx, obj);
326 scope = OBJ_SCOPE(obj);
327 oldproto = JSVAL_TO_OBJECT(LOCKED_OBJ_GET_SLOT(obj, JSSLOT_PROTO));
328 if (oldproto && OBJ_SCOPE(oldproto) == scope) {
329 /* Either obj needs a new empty scope, or it should share pobj's. */
330 if (!pobj ||
331 !OBJ_IS_NATIVE(pobj) ||
332 OBJ_GET_CLASS(cx, pobj) != LOCKED_OBJ_GET_CLASS(oldproto)) {
333 /*
334 * With no proto and no scope of its own, obj is truly empty.
335 *
336 * If pobj is not native, obj needs its own empty scope -- it
337 * should not continue to share oldproto's scope once oldproto
338 * is not on obj's prototype chain. That would put properties
339 * from oldproto's scope ahead of properties defined by pobj,
340 * in lookup order.
341 *
342 * If pobj's class differs from oldproto's, we may need a new
343 * scope to handle differences in private and reserved slots,
344 * so we suboptimally but safely make one.
345 */
346 scope = js_GetMutableScope(cx, obj);
347 if (!scope) {
348 JS_UNLOCK_OBJ(cx, obj);
349 SET_SLOT_DONE(rt);
350 return JS_FALSE;
351 }
352 } else if (OBJ_SCOPE(pobj) != scope) {
353 #ifdef JS_THREADSAFE
354 /*
355 * We are about to nest scope locks. Help jslock.c:ShareScope
356 * keep scope->u.count balanced for the JS_UNLOCK_SCOPE, while
357 * avoiding deadlock, by recording scope in rt->setSlotScope.
358 */
359 if (scope->ownercx) {
360 JS_ASSERT(scope->ownercx == cx);
361 rt->setSlotScope = scope;
362 }
363 #endif
365 /* We can't deadlock because we checked for cycles above (2). */
366 JS_LOCK_OBJ(cx, pobj);
367 newscope = (JSScope *) js_HoldObjectMap(cx, pobj->map);
368 obj->map = &newscope->map;
369 js_DropObjectMap(cx, &scope->map, obj);
370 JS_TRANSFER_SCOPE_LOCK(cx, scope, newscope);
371 scope = newscope;
372 #ifdef JS_THREADSAFE
373 rt->setSlotScope = NULL;
374 #endif
375 }
376 }
377 LOCKED_OBJ_SET_SLOT(obj, JSSLOT_PROTO, OBJECT_TO_JSVAL(pobj));
378 JS_UNLOCK_SCOPE(cx, scope);
379 } else {
380 OBJ_SET_SLOT(cx, obj, slot, OBJECT_TO_JSVAL(pobj));
381 }
383 SET_SLOT_DONE(rt);
384 return JS_TRUE;
386 #undef SET_SLOT_DONE
387 }
389 JS_STATIC_DLL_CALLBACK(JSHashNumber)
390 js_hash_object(const void *key)
391 {
392 return (JSHashNumber)JS_PTR_TO_UINT32(key) >> JSVAL_TAGBITS;
393 }
395 static JSHashEntry *
396 MarkSharpObjects(JSContext *cx, JSObject *obj, JSIdArray **idap)
397 {
398 JSSharpObjectMap *map;
399 JSHashTable *table;
400 JSHashNumber hash;
401 JSHashEntry **hep, *he;
402 jsatomid sharpid;
403 JSIdArray *ida;
404 JSBool ok;
405 jsint i, length;
406 jsid id;
407 #if JS_HAS_GETTER_SETTER
408 JSObject *obj2;
409 JSProperty *prop;
410 uintN attrs;
411 #endif
412 jsval val;
413 int stackDummy;
415 if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) {
416 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED);
417 return NULL;
418 }
420 map = &cx->sharpObjectMap;
421 table = map->table;
422 hash = js_hash_object(obj);
423 hep = JS_HashTableRawLookup(table, hash, obj);
424 he = *hep;
425 if (!he) {
426 sharpid = 0;
427 he = JS_HashTableRawAdd(table, hep, hash, obj,
428 JS_UINT32_TO_PTR(sharpid));
429 if (!he) {
430 JS_ReportOutOfMemory(cx);
431 return NULL;
432 }
434 /*
435 * Increment map->depth to protect js_EnterSharpObject from reentering
436 * itself badly. Without this fix, if we reenter the basis case where
437 * map->depth == 0, when unwinding the inner call we will destroy the
438 * newly-created hash table and crash.
439 */
440 ++map->depth;
441 ida = JS_Enumerate(cx, obj);
442 --map->depth;
443 if (!ida)
444 return NULL;
446 ok = JS_TRUE;
447 for (i = 0, length = ida->length; i < length; i++) {
448 id = ida->vector[i];
449 #if JS_HAS_GETTER_SETTER
450 ok = OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop);
451 if (!ok)
452 break;
453 if (!prop)
454 continue;
455 ok = OBJ_GET_ATTRIBUTES(cx, obj2, id, prop, &attrs);
456 if (ok) {
457 if (OBJ_IS_NATIVE(obj2) &&
458 (attrs & (JSPROP_GETTER | JSPROP_SETTER))) {
459 val = JSVAL_NULL;
460 if (attrs & JSPROP_GETTER)
461 val = (jsval) ((JSScopeProperty*)prop)->getter;
462 if (attrs & JSPROP_SETTER) {
463 if (val != JSVAL_NULL) {
464 /* Mark the getter, then set val to setter. */
465 ok = (MarkSharpObjects(cx, JSVAL_TO_OBJECT(val),
466 NULL)
467 != NULL);
468 }
469 val = (jsval) ((JSScopeProperty*)prop)->setter;
470 }
471 } else {
472 ok = OBJ_GET_PROPERTY(cx, obj, id, &val);
473 }
474 }
475 OBJ_DROP_PROPERTY(cx, obj2, prop);
476 #else
477 ok = OBJ_GET_PROPERTY(cx, obj, id, &val);
478 #endif
479 if (!ok)
480 break;
481 if (!JSVAL_IS_PRIMITIVE(val) &&
482 !MarkSharpObjects(cx, JSVAL_TO_OBJECT(val), NULL)) {
483 ok = JS_FALSE;
484 break;
485 }
486 }
487 if (!ok || !idap)
488 JS_DestroyIdArray(cx, ida);
489 if (!ok)
490 return NULL;
491 } else {
492 sharpid = JS_PTR_TO_UINT32(he->value);
493 if (sharpid == 0) {
494 sharpid = ++map->sharpgen << SHARP_ID_SHIFT;
495 he->value = JS_UINT32_TO_PTR(sharpid);
496 }
497 ida = NULL;
498 }
499 if (idap)
500 *idap = ida;
501 return he;
502 }
504 JSHashEntry *
505 js_EnterSharpObject(JSContext *cx, JSObject *obj, JSIdArray **idap,
506 jschar **sp)
507 {
508 JSSharpObjectMap *map;
509 JSHashTable *table;
510 JSIdArray *ida;
511 JSHashNumber hash;
512 JSHashEntry *he, **hep;
513 jsatomid sharpid;
514 char buf[20];
515 size_t len;
517 if (JS_HAS_NATIVE_BRANCH_CALLBACK_OPTION(cx) &&
518 cx->branchCallback &&
519 !cx->branchCallback(cx, NULL)) {
520 return NULL;
521 }
523 /* Set to null in case we return an early error. */
524 *sp = NULL;
525 map = &cx->sharpObjectMap;
526 table = map->table;
527 if (!table) {
528 table = JS_NewHashTable(8, js_hash_object, JS_CompareValues,
529 JS_CompareValues, NULL, NULL);
530 if (!table) {
531 JS_ReportOutOfMemory(cx);
532 return NULL;
533 }
534 map->table = table;
535 JS_KEEP_ATOMS(cx->runtime);
536 }
538 /* From this point the control must flow either through out: or bad:. */
539 ida = NULL;
540 if (map->depth == 0) {
541 he = MarkSharpObjects(cx, obj, &ida);
542 if (!he)
543 goto bad;
544 JS_ASSERT((JS_PTR_TO_UINT32(he->value) & SHARP_BIT) == 0);
545 if (!idap) {
546 JS_DestroyIdArray(cx, ida);
547 ida = NULL;
548 }
549 } else {
550 hash = js_hash_object(obj);
551 hep = JS_HashTableRawLookup(table, hash, obj);
552 he = *hep;
554 /*
555 * It's possible that the value of a property has changed from the
556 * first time the object's properties are traversed (when the property
557 * ids are entered into the hash table) to the second (when they are
558 * converted to strings), i.e., the OBJ_GET_PROPERTY() call is not
559 * idempotent.
560 */
561 if (!he) {
562 he = JS_HashTableRawAdd(table, hep, hash, obj, NULL);
563 if (!he) {
564 JS_ReportOutOfMemory(cx);
565 goto bad;
566 }
567 sharpid = 0;
568 goto out;
569 }
570 }
572 sharpid = JS_PTR_TO_UINT32(he->value);
573 if (sharpid != 0) {
574 len = JS_snprintf(buf, sizeof buf, "#%u%c",
575 sharpid >> SHARP_ID_SHIFT,
576 (sharpid & SHARP_BIT) ? '#' : '=');
577 *sp = js_InflateString(cx, buf, &len);
578 if (!*sp) {
579 if (ida)
580 JS_DestroyIdArray(cx, ida);
581 goto bad;
582 }
583 }
585 out:
586 JS_ASSERT(he);
587 if ((sharpid & SHARP_BIT) == 0) {
588 if (idap && !ida) {
589 ida = JS_Enumerate(cx, obj);
590 if (!ida) {
591 if (*sp) {
592 JS_free(cx, *sp);
593 *sp = NULL;
594 }
595 goto bad;
596 }
597 }
598 map->depth++;
599 }
601 if (idap)
602 *idap = ida;
603 return he;
605 bad:
606 /* Clean up the sharpObjectMap table on outermost error. */
607 if (map->depth == 0) {
608 JS_UNKEEP_ATOMS(cx->runtime);
609 map->sharpgen = 0;
610 JS_HashTableDestroy(map->table);
611 map->table = NULL;
612 }
613 return NULL;
614 }
616 void
617 js_LeaveSharpObject(JSContext *cx, JSIdArray **idap)
618 {
619 JSSharpObjectMap *map;
620 JSIdArray *ida;
622 map = &cx->sharpObjectMap;
623 JS_ASSERT(map->depth > 0);
624 if (--map->depth == 0) {
625 JS_UNKEEP_ATOMS(cx->runtime);
626 map->sharpgen = 0;
627 JS_HashTableDestroy(map->table);
628 map->table = NULL;
629 }
630 if (idap) {
631 ida = *idap;
632 if (ida) {
633 JS_DestroyIdArray(cx, ida);
634 *idap = NULL;
635 }
636 }
637 }
639 JS_STATIC_DLL_CALLBACK(intN)
640 gc_sharp_table_entry_marker(JSHashEntry *he, intN i, void *arg)
641 {
642 GC_MARK((JSContext *)arg, (JSObject *)he->key, "sharp table entry", NULL);
643 return JS_DHASH_NEXT;
644 }
646 void
647 js_GCMarkSharpMap(JSContext *cx, JSSharpObjectMap *map)
648 {
649 JS_ASSERT(map->depth > 0);
650 JS_ASSERT(map->table);
652 /*
653 * During recursive calls to MarkSharpObjects a non-native object or
654 * object with a custom getProperty method can potentially return an
655 * unrooted value or even cut from the object graph an argument of one of
656 * MarkSharpObjects recursive invocations. So we must protect map->table
657 * entries against GC.
658 *
659 * We can not simply use JSTempValueRooter to mark the obj argument of
660 * MarkSharpObjects during recursion as we have to protect *all* entries
661 * in JSSharpObjectMap including those that contains otherwise unreachable
662 * objects just allocated through custom getProperty. Otherwise newer
663 * allocations can re-use the address of an object stored in the hashtable
664 * confusing js_EnterSharpObject. So to address the problem we simply
665 * mark all objects from map->table.
666 *
667 * An alternative "proper" solution is to use JSTempValueRooter in
668 * MarkSharpObjects with code to remove during finalization entries
669 * with otherwise unreachable objects. But this is way too complex
670 * to justify spending efforts.
671 */
672 JS_HashTableEnumerateEntries(map->table, gc_sharp_table_entry_marker, cx);
673 }
675 #define OBJ_TOSTRING_EXTRA 4 /* for 4 local GC roots */
677 #if JS_HAS_INITIALIZERS || JS_HAS_TOSOURCE
678 JSBool
679 js_obj_toSource(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
680 jsval *rval)
681 {
682 JSBool ok, outermost;
683 JSHashEntry *he;
684 JSIdArray *ida;
685 jschar *chars, *ochars, *vsharp;
686 const jschar *idstrchars, *vchars;
687 size_t nchars, idstrlength, gsoplength, vlength, vsharplength, curlen;
688 char *comma;
689 jsint i, j, length, valcnt;
690 jsid id;
691 #if JS_HAS_GETTER_SETTER
692 JSObject *obj2;
693 JSProperty *prop;
694 uintN attrs;
695 #endif
696 jsval *val;
697 JSString *gsop[2];
698 JSAtom *atom;
699 JSString *idstr, *valstr, *str;
700 int stackDummy;
702 if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) {
703 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED);
704 return JS_FALSE;
705 }
707 /*
708 * obj_toString for 1.2 calls toSource, and doesn't want the extra parens
709 * on the outside.
710 */
711 outermost = !JS_VERSION_IS_1_2(cx) && cx->sharpObjectMap.depth == 0;
712 he = js_EnterSharpObject(cx, obj, &ida, &chars);
713 if (!he)
714 return JS_FALSE;
715 if (IS_SHARP(he)) {
716 /*
717 * We didn't enter -- obj is already "sharp", meaning we've visited it
718 * already in our depth first search, and therefore chars contains a
719 * string of the form "#n#".
720 */
721 JS_ASSERT(!ida);
722 #if JS_HAS_SHARP_VARS
723 nchars = js_strlen(chars);
724 #else
725 chars[0] = '{';
726 chars[1] = '}';
727 chars[2] = 0;
728 nchars = 2;
729 #endif
730 goto make_string;
731 }
732 JS_ASSERT(ida);
733 ok = JS_TRUE;
735 if (!chars) {
736 /* If outermost, allocate 4 + 1 for "({})" and the terminator. */
737 chars = (jschar *) malloc(((outermost ? 4 : 2) + 1) * sizeof(jschar));
738 nchars = 0;
739 if (!chars)
740 goto error;
741 if (outermost)
742 chars[nchars++] = '(';
743 } else {
744 /* js_EnterSharpObject returned a string of the form "#n=" in chars. */
745 MAKE_SHARP(he);
746 nchars = js_strlen(chars);
747 chars = (jschar *)
748 realloc((ochars = chars), (nchars + 2 + 1) * sizeof(jschar));
749 if (!chars) {
750 free(ochars);
751 goto error;
752 }
753 if (outermost) {
754 /*
755 * No need for parentheses around the whole shebang, because #n=
756 * unambiguously begins an object initializer, and never a block
757 * statement.
758 */
759 outermost = JS_FALSE;
760 }
761 }
763 #ifdef DUMP_CALL_TABLE
764 if (cx->options & JSOPTION_LOGCALL_TOSOURCE) {
765 const char *classname = OBJ_GET_CLASS(cx, obj)->name;
766 size_t classnchars = strlen(classname);
767 static const char classpropid[] = "C";
768 const char *cp;
769 size_t onchars = nchars;
771 /* 2 for ': ', 2 quotes around classname, 2 for ', ' after. */
772 classnchars += sizeof classpropid - 1 + 2 + 2;
773 if (ida->length)
774 classnchars += 2;
776 /* 2 for the braces, 1 for the terminator */
777 chars = (jschar *)
778 realloc((ochars = chars),
779 (nchars + classnchars + 2 + 1) * sizeof(jschar));
780 if (!chars) {
781 free(ochars);
782 goto error;
783 }
785 chars[nchars++] = '{'; /* 1 from the 2 braces */
786 for (cp = classpropid; *cp; cp++)
787 chars[nchars++] = (jschar) *cp;
788 chars[nchars++] = ':';
789 chars[nchars++] = ' '; /* 2 for ': ' */
790 chars[nchars++] = '"';
791 for (cp = classname; *cp; cp++)
792 chars[nchars++] = (jschar) *cp;
793 chars[nchars++] = '"'; /* 2 quotes */
794 if (ida->length) {
795 chars[nchars++] = ',';
796 chars[nchars++] = ' '; /* 2 for ', ' */
797 }
799 JS_ASSERT(nchars - onchars == 1 + classnchars);
800 } else
801 #endif
802 chars[nchars++] = '{';
804 comma = NULL;
806 /*
807 * We have four local roots for cooked and raw value GC safety. Hoist the
808 * "argv + 2" out of the loop using the val local, which refers to the raw
809 * (unconverted, "uncooked") values.
810 */
811 val = argv + 2;
813 for (i = 0, length = ida->length; i < length; i++) {
814 /* Get strings for id and value and GC-root them via argv. */
815 id = ida->vector[i];
817 #if JS_HAS_GETTER_SETTER
819 ok = OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop);
820 if (!ok)
821 goto error;
822 valcnt = 0;
823 if (prop) {
824 ok = OBJ_GET_ATTRIBUTES(cx, obj2, id, prop, &attrs);
825 if (!ok) {
826 OBJ_DROP_PROPERTY(cx, obj2, prop);
827 goto error;
828 }
829 if (OBJ_IS_NATIVE(obj2) &&
830 (attrs & (JSPROP_GETTER | JSPROP_SETTER))) {
831 if (attrs & JSPROP_GETTER) {
832 val[valcnt] = (jsval) ((JSScopeProperty *)prop)->getter;
833 #ifdef OLD_GETTER_SETTER
834 gsop[valcnt] =
835 ATOM_TO_STRING(cx->runtime->atomState.getterAtom);
836 #else
837 gsop[valcnt] =
838 ATOM_TO_STRING(cx->runtime->atomState.getAtom);
839 #endif
840 valcnt++;
841 }
842 if (attrs & JSPROP_SETTER) {
843 val[valcnt] = (jsval) ((JSScopeProperty *)prop)->setter;
844 #ifdef OLD_GETTER_SETTER
845 gsop[valcnt] =
846 ATOM_TO_STRING(cx->runtime->atomState.setterAtom);
847 #else
848 gsop[valcnt] =
849 ATOM_TO_STRING(cx->runtime->atomState.setAtom);
850 #endif
851 valcnt++;
852 }
853 } else {
854 valcnt = 1;
855 gsop[0] = NULL;
856 ok = OBJ_GET_PROPERTY(cx, obj, id, &val[0]);
857 }
858 OBJ_DROP_PROPERTY(cx, obj2, prop);
859 }
861 #else /* !JS_HAS_GETTER_SETTER */
863 valcnt = 1;
864 gsop[0] = NULL;
865 ok = OBJ_GET_PROPERTY(cx, obj, id, &val[0]);
867 #endif /* !JS_HAS_GETTER_SETTER */
869 if (!ok)
870 goto error;
872 /* Convert id to a jsval and then to a string. */
873 atom = JSID_IS_ATOM(id) ? JSID_TO_ATOM(id) : NULL;
874 id = ID_TO_VALUE(id);
875 idstr = js_ValueToString(cx, id);
876 if (!idstr) {
877 ok = JS_FALSE;
878 goto error;
879 }
880 *rval = STRING_TO_JSVAL(idstr); /* local root */
882 /*
883 * If id is a string that's a reserved identifier, or else id is not
884 * an identifier at all, then it needs to be quoted. Also, negative
885 * integer ids must be quoted.
886 */
887 if (atom
888 ? (ATOM_KEYWORD(atom) || !js_IsIdentifier(idstr))
889 : (JSID_IS_OBJECT(id) || JSID_TO_INT(id) < 0)) {
890 idstr = js_QuoteString(cx, idstr, (jschar)'\'');
891 if (!idstr) {
892 ok = JS_FALSE;
893 goto error;
894 }
895 *rval = STRING_TO_JSVAL(idstr); /* local root */
896 }
897 idstrchars = JSSTRING_CHARS(idstr);
898 idstrlength = JSSTRING_LENGTH(idstr);
900 for (j = 0; j < valcnt; j++) {
901 /* Convert val[j] to its canonical source form. */
902 valstr = js_ValueToSource(cx, val[j]);
903 if (!valstr) {
904 ok = JS_FALSE;
905 goto error;
906 }
907 argv[j] = STRING_TO_JSVAL(valstr); /* local root */
908 vchars = JSSTRING_CHARS(valstr);
909 vlength = JSSTRING_LENGTH(valstr);
911 #ifndef OLD_GETTER_SETTER
912 /*
913 * Remove '(function ' from the beginning of valstr and ')' from the
914 * end so that we can put "get" in front of the function definition.
915 */
916 if (gsop[j]) {
917 int n = strlen(js_function_str) + 2;
918 vchars += n;
919 vlength -= n + 1;
920 }
921 #endif
923 /* If val[j] is a non-sharp object, consider sharpening it. */
924 vsharp = NULL;
925 vsharplength = 0;
926 #if JS_HAS_SHARP_VARS
927 if (!JSVAL_IS_PRIMITIVE(val[j]) && vchars[0] != '#') {
928 he = js_EnterSharpObject(cx, JSVAL_TO_OBJECT(val[j]), NULL,
929 &vsharp);
930 if (!he) {
931 ok = JS_FALSE;
932 goto error;
933 }
934 if (IS_SHARP(he)) {
935 vchars = vsharp;
936 vlength = js_strlen(vchars);
937 } else {
938 if (vsharp) {
939 vsharplength = js_strlen(vsharp);
940 MAKE_SHARP(he);
941 }
942 js_LeaveSharpObject(cx, NULL);
943 }
944 }
945 #endif
947 #define SAFE_ADD(n) \
948 JS_BEGIN_MACRO \
949 size_t n_ = (n); \
950 curlen += n_; \
951 if (curlen < n_) \
952 goto overflow; \
953 JS_END_MACRO
955 curlen = nchars;
956 if (comma)
957 SAFE_ADD(2);
958 SAFE_ADD(idstrlength + 1);
959 if (gsop[j])
960 SAFE_ADD(JSSTRING_LENGTH(gsop[j]) + 1);
961 SAFE_ADD(vsharplength);
962 SAFE_ADD(vlength);
963 SAFE_ADD((outermost ? 2 : 1) + 1);
964 #undef SAFE_ADD
966 if (curlen > (size_t)-1 / sizeof(jschar))
967 goto overflow;
969 /* Allocate 1 + 1 at end for closing brace and terminating 0. */
970 chars = (jschar *)
971 realloc((ochars = chars), curlen * sizeof(jschar));
972 if (!chars) {
973 /* Save code space on error: let JS_free ignore null vsharp. */
974 JS_free(cx, vsharp);
975 free(ochars);
976 goto error;
977 }
979 if (comma) {
980 chars[nchars++] = comma[0];
981 chars[nchars++] = comma[1];
982 }
983 comma = ", ";
985 #ifdef OLD_GETTER_SETTER
986 js_strncpy(&chars[nchars], idstrchars, idstrlength);
987 nchars += idstrlength;
988 if (gsop[j]) {
989 chars[nchars++] = ' ';
990 gsoplength = JSSTRING_LENGTH(gsop[j]);
991 js_strncpy(&chars[nchars], JSSTRING_CHARS(gsop[j]), gsoplength);
992 nchars += gsoplength;
993 }
994 chars[nchars++] = ':';
995 #else
996 if (gsop[j]) {
997 gsoplength = JSSTRING_LENGTH(gsop[j]);
998 js_strncpy(&chars[nchars], JSSTRING_CHARS(gsop[j]), gsoplength);
999 nchars += gsoplength;
1000 chars[nchars++] = ' ';
1001 }
1002 js_strncpy(&chars[nchars], idstrchars, idstrlength);
1003 nchars += idstrlength;
1004 if (!gsop[j])
1005 chars[nchars++] = ':';
1006 #endif
1007 if (vsharplength) {
1008 js_strncpy(&chars[nchars], vsharp, vsharplength);
1009 nchars += vsharplength;
1010 }
1011 js_strncpy(&chars[nchars], vchars, vlength);
1012 nchars += vlength;
1014 if (vsharp)
1015 JS_free(cx, vsharp);
1016 #ifdef DUMP_CALL_TABLE
1017 if (outermost && nchars >= js_LogCallToSourceLimit)
1018 break;
1019 #endif
1020 }
1021 }
1023 chars[nchars++] = '}';
1024 if (outermost)
1025 chars[nchars++] = ')';
1026 chars[nchars] = 0;
1028 error:
1029 js_LeaveSharpObject(cx, &ida);
1031 if (!ok) {
1032 if (chars)
1033 free(chars);
1034 return ok;
1035 }
1037 if (!chars) {
1038 JS_ReportOutOfMemory(cx);
1039 return JS_FALSE;
1040 }
1041 make_string:
1042 str = js_NewString(cx, chars, nchars, 0);
1043 if (!str) {
1044 free(chars);
1045 return JS_FALSE;
1046 }
1047 *rval = STRING_TO_JSVAL(str);
1048 return JS_TRUE;
1050 overflow:
1051 JS_free(cx, vsharp);
1052 free(chars);
1053 chars = NULL;
1054 goto error;
1055 }
1056 #endif /* JS_HAS_INITIALIZERS || JS_HAS_TOSOURCE */
1058 JSBool
1059 js_obj_toString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1060 jsval *rval)
1061 {
1062 jschar *chars;
1063 size_t nchars;
1064 const char *clazz, *prefix;
1065 JSString *str;
1067 #if JS_HAS_INITIALIZERS
1068 if (JS_VERSION_IS_1_2(cx))
1069 return js_obj_toSource(cx, obj, argc, argv, rval);
1070 #endif
1072 clazz = OBJ_GET_CLASS(cx, obj)->name;
1073 nchars = 9 + strlen(clazz); /* 9 for "[object ]" */
1074 chars = (jschar *) JS_malloc(cx, (nchars + 1) * sizeof(jschar));
1075 if (!chars)
1076 return JS_FALSE;
1078 prefix = "[object ";
1079 nchars = 0;
1080 while ((chars[nchars] = (jschar)*prefix) != 0)
1081 nchars++, prefix++;
1082 while ((chars[nchars] = (jschar)*clazz) != 0)
1083 nchars++, clazz++;
1084 chars[nchars++] = ']';
1085 chars[nchars] = 0;
1087 str = js_NewString(cx, chars, nchars, 0);
1088 if (!str) {
1089 JS_free(cx, chars);
1090 return JS_FALSE;
1091 }
1092 *rval = STRING_TO_JSVAL(str);
1093 return JS_TRUE;
1094 }
1096 static JSBool
1097 js_obj_toLocaleString(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1098 jsval *rval)
1099 {
1100 JSString *str;
1102 str = js_ValueToString(cx, argv[-1]);
1103 if (!str)
1104 return JS_FALSE;
1106 *rval = STRING_TO_JSVAL(str);
1107 return JS_TRUE;
1108 }
1110 static JSBool
1111 obj_valueOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1112 {
1113 *rval = OBJECT_TO_JSVAL(obj);
1114 return JS_TRUE;
1115 }
1117 /*
1118 * Check whether principals subsumes scopeobj's principals, and return true
1119 * if so (or if scopeobj has no principals, for backward compatibility with
1120 * the JS API, which does not require principals), and false otherwise.
1121 */
1122 JSBool
1123 js_CheckPrincipalsAccess(JSContext *cx, JSObject *scopeobj,
1124 JSPrincipals *principals, const char *caller)
1125 {
1126 JSRuntime *rt;
1127 JSPrincipals *scopePrincipals;
1129 rt = cx->runtime;
1130 if (rt->findObjectPrincipals) {
1131 scopePrincipals = rt->findObjectPrincipals(cx, scopeobj);
1132 if (!principals || !scopePrincipals ||
1133 !principals->subsume(principals, scopePrincipals)) {
1134 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
1135 JSMSG_BAD_INDIRECT_CALL, caller);
1136 return JS_FALSE;
1137 }
1138 }
1139 return JS_TRUE;
1140 }
1142 JSObject *
1143 js_CheckScopeChainValidity(JSContext *cx, JSObject *scopeobj, const char *caller)
1144 {
1145 JSClass *clasp;
1146 JSExtendedClass *xclasp;
1147 JSObject *inner;
1149 if (!scopeobj)
1150 goto bad;
1152 OBJ_TO_INNER_OBJECT(cx, scopeobj);
1153 if (!scopeobj)
1154 return NULL;
1156 inner = scopeobj;
1158 /* XXX This is an awful gross hack. */
1159 while (scopeobj) {
1160 clasp = OBJ_GET_CLASS(cx, scopeobj);
1161 if (clasp->flags & JSCLASS_IS_EXTENDED) {
1162 xclasp = (JSExtendedClass*)clasp;
1163 if (xclasp->innerObject &&
1164 xclasp->innerObject(cx, scopeobj) != scopeobj) {
1165 goto bad;
1166 }
1167 }
1169 scopeobj = OBJ_GET_PARENT(cx, scopeobj);
1170 }
1172 return inner;
1174 bad:
1175 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
1176 JSMSG_BAD_INDIRECT_CALL, caller);
1177 return NULL;
1178 }
1180 static JSBool
1181 obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1182 {
1183 JSStackFrame *fp, *caller;
1184 JSBool indirectCall;
1185 JSObject *scopeobj;
1186 JSString *str;
1187 const char *file;
1188 uintN line;
1189 JSPrincipals *principals;
1190 JSScript *script;
1191 JSBool ok;
1192 #if JS_HAS_EVAL_THIS_SCOPE
1193 JSObject *callerScopeChain = NULL, *callerVarObj = NULL;
1194 JSObject *setCallerScopeChain = NULL;
1195 JSBool setCallerVarObj = JS_FALSE;
1196 #endif
1198 fp = cx->fp;
1199 caller = JS_GetScriptedCaller(cx, fp);
1200 indirectCall = (caller && caller->pc && *caller->pc != JSOP_EVAL);
1202 if (JS_VERSION_IS_ECMA(cx) &&
1203 indirectCall &&
1204 !JS_ReportErrorFlagsAndNumber(cx,
1205 JSREPORT_WARNING | JSREPORT_STRICT,
1206 js_GetErrorMessage, NULL,
1207 JSMSG_BAD_INDIRECT_CALL,
1208 js_eval_str)) {
1209 return JS_FALSE;
1210 }
1212 if (!JSVAL_IS_STRING(argv[0])) {
1213 *rval = argv[0];
1214 return JS_TRUE;
1215 }
1217 /*
1218 * If the caller is a lightweight function and doesn't have a variables
1219 * object, then we need to provide one for the compiler to stick any
1220 * declared (var) variables into.
1221 */
1222 if (caller && !caller->varobj && !js_GetCallObject(cx, caller, NULL))
1223 return JS_FALSE;
1225 #if JS_HAS_SCRIPT_OBJECT
1226 /*
1227 * Script.prototype.compile/exec and Object.prototype.eval all take an
1228 * optional trailing argument that overrides the scope object.
1229 */
1230 scopeobj = NULL;
1231 if (argc >= 2) {
1232 if (!js_ValueToObject(cx, argv[1], &scopeobj))
1233 return JS_FALSE;
1234 argv[1] = OBJECT_TO_JSVAL(scopeobj);
1235 }
1236 if (!scopeobj)
1237 #endif
1238 {
1239 #if JS_HAS_EVAL_THIS_SCOPE
1240 /* If obj.eval(str), emulate 'with (obj) eval(str)' in the caller. */
1241 if (indirectCall) {
1242 callerScopeChain = caller->scopeChain;
1243 if (obj != callerScopeChain) {
1244 if (!js_CheckPrincipalsAccess(cx, obj,
1245 caller->script->principals,
1246 js_eval_str)) {
1247 return JS_FALSE;
1248 }
1250 scopeobj = js_NewWithObject(cx, obj, callerScopeChain, -1);
1251 if (!scopeobj)
1252 return JS_FALSE;
1254 /* Set fp->scopeChain too, for the compiler. */
1255 caller->scopeChain = fp->scopeChain = scopeobj;
1257 /* Remember scopeobj so we can null its private when done. */
1258 setCallerScopeChain = scopeobj;
1259 }
1261 callerVarObj = caller->varobj;
1262 if (obj != callerVarObj) {
1263 /* Set fp->varobj too, for the compiler. */
1264 caller->varobj = fp->varobj = obj;
1265 setCallerVarObj = JS_TRUE;
1266 }
1267 }
1268 /* From here on, control must exit through label out with ok set. */
1269 #endif
1271 #if JS_BUG_EVAL_THIS_SCOPE
1272 /* An old version used the object in which eval was found for scope. */
1273 scopeobj = obj;
1274 #else
1275 /* Compile using caller's current scope object. */
1276 if (caller)
1277 scopeobj = caller->scopeChain;
1278 #endif
1279 }
1281 /* Ensure we compile this eval with the right object in the scope chain. */
1282 scopeobj = js_CheckScopeChainValidity(cx, scopeobj, js_eval_str);
1283 if (!scopeobj)
1284 return JS_FALSE;
1286 str = JSVAL_TO_STRING(argv[0]);
1287 if (caller) {
1288 file = caller->script->filename;
1289 line = js_PCToLineNumber(cx, caller->script, caller->pc);
1290 principals = JS_EvalFramePrincipals(cx, fp, caller);
1291 } else {
1292 file = NULL;
1293 line = 0;
1294 principals = NULL;
1295 }
1297 /*
1298 * Set JSFRAME_EVAL on fp and any frames (e.g., fun_call if eval.call was
1299 * invoked) between fp and its scripted caller, to help the compiler easily
1300 * find the same caller whose scope and var obj we've set.
1301 *
1302 * XXX this nonsense could, and perhaps should, go away with a better way
1303 * to pass params to the compiler than via the top-most frame.
1304 */
1305 do {
1306 fp->flags |= JSFRAME_EVAL;
1307 } while ((fp = fp->down) != caller);
1309 script = JS_CompileUCScriptForPrincipals(cx, scopeobj, principals,
1310 JSSTRING_CHARS(str),
1311 JSSTRING_LENGTH(str),
1312 file, line);
1313 if (!script) {
1314 ok = JS_FALSE;
1315 goto out;
1316 }
1318 #if !JS_BUG_EVAL_THIS_SCOPE
1319 #if JS_HAS_SCRIPT_OBJECT
1320 if (argc < 2)
1321 #endif
1322 {
1323 /* Execute using caller's new scope object (might be a Call object). */
1324 if (caller)
1325 scopeobj = caller->scopeChain;
1326 }
1327 #endif
1329 /*
1330 * Belt-and-braces: check that the lesser of eval's principals and the
1331 * caller's principals has access to scopeobj.
1332 */
1333 ok = js_CheckPrincipalsAccess(cx, scopeobj, principals, js_eval_str);
1334 if (ok)
1335 ok = js_Execute(cx, scopeobj, script, caller, JSFRAME_EVAL, rval);
1337 JS_DestroyScript(cx, script);
1339 out:
1340 #if JS_HAS_EVAL_THIS_SCOPE
1341 /* Restore OBJ_GET_PARENT(scopeobj) not callerScopeChain in case of Call. */
1342 if (setCallerScopeChain) {
1343 caller->scopeChain = callerScopeChain;
1344 JS_ASSERT(OBJ_GET_CLASS(cx, setCallerScopeChain) == &js_WithClass);
1345 JS_SetPrivate(cx, setCallerScopeChain, NULL);
1346 }
1347 if (setCallerVarObj)
1348 caller->varobj = callerVarObj;
1349 #endif
1350 return ok;
1351 }
1353 #if JS_HAS_OBJ_WATCHPOINT
1355 static JSBool
1356 obj_watch_handler(JSContext *cx, JSObject *obj, jsval id, jsval old, jsval *nvp,
1357 void *closure)
1358 {
1359 JSObject *callable;
1360 JSRuntime *rt;
1361 JSStackFrame *caller;
1362 JSPrincipals *subject, *watcher;
1363 JSResolvingKey key;
1364 JSResolvingEntry *entry;
1365 uint32 generation;
1366 jsval argv[3];
1367 JSBool ok;
1369 callable = (JSObject *) closure;
1371 rt = cx->runtime;
1372 if (rt->findObjectPrincipals) {
1373 /* Skip over any obj_watch_* frames between us and the real subject. */
1374 caller = JS_GetScriptedCaller(cx, cx->fp);
1375 if (caller) {
1376 /*
1377 * Only call the watch handler if the watcher is allowed to watch
1378 * the currently executing script.
1379 */
1380 watcher = rt->findObjectPrincipals(cx, callable);
1381 subject = JS_StackFramePrincipals(cx, caller);
1383 if (watcher && subject && !watcher->subsume(watcher, subject)) {
1384 /* Silently don't call the watch handler. */
1385 return JS_TRUE;
1386 }
1387 }
1388 }
1390 /* Avoid recursion on (obj, id) already being watched on cx. */
1391 key.obj = obj;
1392 key.id = id;
1393 if (!js_StartResolving(cx, &key, JSRESFLAG_WATCH, &entry))
1394 return JS_FALSE;
1395 if (!entry)
1396 return JS_TRUE;
1397 generation = cx->resolvingTable->generation;
1399 argv[0] = id;
1400 argv[1] = old;
1401 argv[2] = *nvp;
1402 ok = js_InternalCall(cx, obj, OBJECT_TO_JSVAL(callable), 3, argv, nvp);
1403 js_StopResolving(cx, &key, JSRESFLAG_WATCH, entry, generation);
1404 return ok;
1405 }
1407 static JSBool
1408 obj_watch(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1409 {
1410 JSObject *callable;
1411 jsval userid, value;
1412 jsid propid;
1413 uintN attrs;
1415 callable = js_ValueToCallableObject(cx, &argv[1], 0);
1416 if (!callable)
1417 return JS_FALSE;
1419 /* Compute the unique int/atom symbol id needed by js_LookupProperty. */
1420 userid = argv[0];
1421 if (!JS_ValueToId(cx, userid, &propid))
1422 return JS_FALSE;
1424 if (!OBJ_CHECK_ACCESS(cx, obj, propid, JSACC_WATCH, &value, &attrs))
1425 return JS_FALSE;
1426 if (attrs & JSPROP_READONLY)
1427 return JS_TRUE;
1428 return JS_SetWatchPoint(cx, obj, userid, obj_watch_handler, callable);
1429 }
1431 static JSBool
1432 obj_unwatch(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1433 {
1434 return JS_ClearWatchPoint(cx, obj, argv[0], NULL, NULL);
1435 }
1437 #endif /* JS_HAS_OBJ_WATCHPOINT */
1439 #if JS_HAS_NEW_OBJ_METHODS
1440 /*
1441 * Prototype and property query methods, to complement the 'in' and
1442 * 'instanceof' operators.
1443 */
1445 /* Proposed ECMA 15.2.4.5. */
1446 static JSBool
1447 obj_hasOwnProperty(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1448 jsval *rval)
1449 {
1450 return js_HasOwnPropertyHelper(cx, obj, obj->map->ops->lookupProperty,
1451 argc, argv, rval);
1452 }
1454 JSBool
1455 js_HasOwnPropertyHelper(JSContext *cx, JSObject *obj, JSLookupPropOp lookup,
1456 uintN argc, jsval *argv, jsval *rval)
1457 {
1458 jsid id;
1459 JSObject *obj2;
1460 JSProperty *prop;
1461 JSScopeProperty *sprop;
1463 if (!JS_ValueToId(cx, argv[0], &id))
1464 return JS_FALSE;
1465 if (!lookup(cx, obj, id, &obj2, &prop))
1466 return JS_FALSE;
1467 if (!prop) {
1468 *rval = JSVAL_FALSE;
1469 } else if (obj2 == obj) {
1470 *rval = JSVAL_TRUE;
1471 } else {
1472 JSClass *clasp;
1473 JSExtendedClass *xclasp;
1475 clasp = OBJ_GET_CLASS(cx, obj);
1476 xclasp = (clasp->flags & JSCLASS_IS_EXTENDED)
1477 ? (JSExtendedClass *)clasp
1478 : NULL;
1479 if (xclasp && xclasp->outerObject &&
1480 xclasp->outerObject(cx, obj2) == obj) {
1481 *rval = JSVAL_TRUE;
1482 } else if (OBJ_IS_NATIVE(obj2) && OBJ_GET_CLASS(cx, obj2) == clasp) {
1483 /*
1484 * The combination of JSPROP_SHARED and JSPROP_PERMANENT in a
1485 * delegated property makes that property appear to be direct in
1486 * all delegating instances of the same native class. This hack
1487 * avoids bloating every function instance with its own 'length'
1488 * (AKA 'arity') property. But it must not extend across class
1489 * boundaries, to avoid making hasOwnProperty lie (bug 320854).
1490 *
1491 * It's not really a hack, of course: a permanent property can't
1492 * be deleted, and JSPROP_SHARED means "don't allocate a slot in
1493 * any instance, prototype or delegating". Without a slot, and
1494 * without the ability to remove and recreate (with differences)
1495 * the property, there is no way to tell whether it is directly
1496 * owned, or indirectly delegated.
1497 */
1498 sprop = (JSScopeProperty *)prop;
1499 *rval = BOOLEAN_TO_JSVAL(SPROP_IS_SHARED_PERMANENT(sprop));
1500 } else {
1501 *rval = JSVAL_FALSE;
1502 }
1503 }
1504 if (prop)
1505 OBJ_DROP_PROPERTY(cx, obj2, prop);
1506 return JS_TRUE;
1507 }
1509 /* Proposed ECMA 15.2.4.6. */
1510 static JSBool
1511 obj_isPrototypeOf(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1512 jsval *rval)
1513 {
1514 JSBool b;
1516 if (!js_IsDelegate(cx, obj, *argv, &b))
1517 return JS_FALSE;
1518 *rval = BOOLEAN_TO_JSVAL(b);
1519 return JS_TRUE;
1520 }
1522 /* Proposed ECMA 15.2.4.7. */
1523 static JSBool
1524 obj_propertyIsEnumerable(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1525 jsval *rval)
1526 {
1527 jsid id;
1528 uintN attrs;
1529 JSObject *obj2;
1530 JSProperty *prop;
1531 JSBool ok;
1533 if (!JS_ValueToId(cx, argv[0], &id))
1534 return JS_FALSE;
1536 if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop))
1537 return JS_FALSE;
1539 if (!prop) {
1540 *rval = JSVAL_FALSE;
1541 return JS_TRUE;
1542 }
1544 /*
1545 * XXX ECMA spec error compatible: return false unless hasOwnProperty.
1546 * The ECMA spec really should be fixed so propertyIsEnumerable and the
1547 * for..in loop agree on whether prototype properties are enumerable,
1548 * obviously by fixing this method (not by breaking the for..in loop!).
1549 *
1550 * We check here for shared permanent prototype properties, which should
1551 * be treated as if they are local to obj. They are an implementation
1552 * technique used to satisfy ECMA requirements; users should not be able
1553 * to distinguish a shared permanent proto-property from a local one.
1554 */
1555 if (obj2 != obj &&
1556 !(OBJ_IS_NATIVE(obj2) &&
1557 SPROP_IS_SHARED_PERMANENT((JSScopeProperty *)prop))) {
1558 OBJ_DROP_PROPERTY(cx, obj2, prop);
1559 *rval = JSVAL_FALSE;
1560 return JS_TRUE;
1561 }
1563 ok = OBJ_GET_ATTRIBUTES(cx, obj2, id, prop, &attrs);
1564 OBJ_DROP_PROPERTY(cx, obj2, prop);
1565 if (ok)
1566 *rval = BOOLEAN_TO_JSVAL((attrs & JSPROP_ENUMERATE) != 0);
1567 return ok;
1568 }
1569 #endif /* JS_HAS_NEW_OBJ_METHODS */
1571 #if JS_HAS_GETTER_SETTER
1572 static JSBool
1573 obj_defineGetter(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1574 jsval *rval)
1575 {
1576 jsval fval, junk;
1577 jsid id;
1578 uintN attrs;
1580 fval = argv[1];
1581 if (JS_TypeOfValue(cx, fval) != JSTYPE_FUNCTION) {
1582 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
1583 JSMSG_BAD_GETTER_OR_SETTER,
1584 js_getter_str);
1585 return JS_FALSE;
1586 }
1588 if (!JS_ValueToId(cx, argv[0], &id))
1589 return JS_FALSE;
1590 if (!js_CheckRedeclaration(cx, obj, id, JSPROP_GETTER, NULL, NULL))
1591 return JS_FALSE;
1592 /*
1593 * Getters and setters are just like watchpoints from an access
1594 * control point of view.
1595 */
1596 if (!OBJ_CHECK_ACCESS(cx, obj, id, JSACC_WATCH, &junk, &attrs))
1597 return JS_FALSE;
1598 return OBJ_DEFINE_PROPERTY(cx, obj, id, JSVAL_VOID,
1599 (JSPropertyOp) JSVAL_TO_OBJECT(fval), NULL,
1600 JSPROP_ENUMERATE | JSPROP_GETTER | JSPROP_SHARED,
1601 NULL);
1602 }
1604 static JSBool
1605 obj_defineSetter(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1606 jsval *rval)
1607 {
1608 jsval fval, junk;
1609 jsid id;
1610 uintN attrs;
1612 fval = argv[1];
1613 if (JS_TypeOfValue(cx, fval) != JSTYPE_FUNCTION) {
1614 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
1615 JSMSG_BAD_GETTER_OR_SETTER,
1616 js_setter_str);
1617 return JS_FALSE;
1618 }
1620 if (!JS_ValueToId(cx, argv[0], &id))
1621 return JS_FALSE;
1622 if (!js_CheckRedeclaration(cx, obj, id, JSPROP_SETTER, NULL, NULL))
1623 return JS_FALSE;
1624 /*
1625 * Getters and setters are just like watchpoints from an access
1626 * control point of view.
1627 */
1628 if (!OBJ_CHECK_ACCESS(cx, obj, id, JSACC_WATCH, &junk, &attrs))
1629 return JS_FALSE;
1630 return OBJ_DEFINE_PROPERTY(cx, obj, id, JSVAL_VOID,
1631 NULL, (JSPropertyOp) JSVAL_TO_OBJECT(fval),
1632 JSPROP_ENUMERATE | JSPROP_SETTER | JSPROP_SHARED,
1633 NULL);
1634 }
1636 static JSBool
1637 obj_lookupGetter(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1638 jsval *rval)
1639 {
1640 jsid id;
1641 JSObject *pobj;
1642 JSProperty *prop;
1643 JSScopeProperty *sprop;
1645 if (!JS_ValueToId(cx, argv[0], &id))
1646 return JS_FALSE;
1647 if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &pobj, &prop))
1648 return JS_FALSE;
1649 if (prop) {
1650 if (OBJ_IS_NATIVE(pobj)) {
1651 sprop = (JSScopeProperty *) prop;
1652 if (sprop->attrs & JSPROP_GETTER)
1653 *rval = OBJECT_TO_JSVAL(sprop->getter);
1654 }
1655 OBJ_DROP_PROPERTY(cx, pobj, prop);
1656 }
1657 return JS_TRUE;
1658 }
1660 static JSBool
1661 obj_lookupSetter(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
1662 jsval *rval)
1663 {
1664 jsid id;
1665 JSObject *pobj;
1666 JSProperty *prop;
1667 JSScopeProperty *sprop;
1669 if (!JS_ValueToId(cx, argv[0], &id))
1670 return JS_FALSE;
1671 if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &pobj, &prop))
1672 return JS_FALSE;
1673 if (prop) {
1674 if (OBJ_IS_NATIVE(pobj)) {
1675 sprop = (JSScopeProperty *) prop;
1676 if (sprop->attrs & JSPROP_SETTER)
1677 *rval = OBJECT_TO_JSVAL(sprop->setter);
1678 }
1679 OBJ_DROP_PROPERTY(cx, pobj, prop);
1680 }
1681 return JS_TRUE;
1682 }
1683 #endif /* JS_HAS_GETTER_SETTER */
1685 #if JS_HAS_OBJ_WATCHPOINT
1686 const char js_watch_str[] = "watch";
1687 const char js_unwatch_str[] = "unwatch";
1688 #endif
1689 #if JS_HAS_NEW_OBJ_METHODS
1690 const char js_hasOwnProperty_str[] = "hasOwnProperty";
1691 const char js_isPrototypeOf_str[] = "isPrototypeOf";
1692 const char js_propertyIsEnumerable_str[] = "propertyIsEnumerable";
1693 #endif
1694 #if JS_HAS_GETTER_SETTER
1695 const char js_defineGetter_str[] = "__defineGetter__";
1696 const char js_defineSetter_str[] = "__defineSetter__";
1697 const char js_lookupGetter_str[] = "__lookupGetter__";
1698 const char js_lookupSetter_str[] = "__lookupSetter__";
1699 #endif
1701 static JSFunctionSpec object_methods[] = {
1702 #if JS_HAS_TOSOURCE
1703 {js_toSource_str, js_obj_toSource, 0, 0, OBJ_TOSTRING_EXTRA},
1704 #endif
1705 {js_toString_str, js_obj_toString, 0, 0, OBJ_TOSTRING_EXTRA},
1706 {js_toLocaleString_str, js_obj_toLocaleString, 0, 0, OBJ_TOSTRING_EXTRA},
1707 {js_valueOf_str, obj_valueOf, 0,0,0},
1708 {js_eval_str, obj_eval, 1,0,0},
1709 #if JS_HAS_OBJ_WATCHPOINT
1710 {js_watch_str, obj_watch, 2,0,0},
1711 {js_unwatch_str, obj_unwatch, 1,0,0},
1712 #endif
1713 #if JS_HAS_NEW_OBJ_METHODS
1714 {js_hasOwnProperty_str, obj_hasOwnProperty, 1,0,0},
1715 {js_isPrototypeOf_str, obj_isPrototypeOf, 1,0,0},
1716 {js_propertyIsEnumerable_str, obj_propertyIsEnumerable, 1,0,0},
1717 #endif
1718 #if JS_HAS_GETTER_SETTER
1719 {js_defineGetter_str, obj_defineGetter, 2,0,0},
1720 {js_defineSetter_str, obj_defineSetter, 2,0,0},
1721 {js_lookupGetter_str, obj_lookupGetter, 1,0,0},
1722 {js_lookupSetter_str, obj_lookupSetter, 1,0,0},
1723 #endif
1724 {0,0,0,0,0}
1725 };
1727 static JSBool
1728 Object(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1729 {
1730 if (argc == 0) {
1731 /* Trigger logic below to construct a blank object. */
1732 obj = NULL;
1733 } else {
1734 /* If argv[0] is null or undefined, obj comes back null. */
1735 if (!js_ValueToObject(cx, argv[0], &obj))
1736 return JS_FALSE;
1737 }
1738 if (!obj) {
1739 JS_ASSERT(!argc || JSVAL_IS_NULL(argv[0]) || JSVAL_IS_VOID(argv[0]));
1740 if (cx->fp->flags & JSFRAME_CONSTRUCTING)
1741 return JS_TRUE;
1742 obj = js_NewObject(cx, &js_ObjectClass, NULL, NULL);
1743 if (!obj)
1744 return JS_FALSE;
1745 }
1746 *rval = OBJECT_TO_JSVAL(obj);
1747 return JS_TRUE;
1748 }
1750 /*
1751 * ObjectOps and Class for with-statement stack objects.
1752 */
1753 static JSBool
1754 with_LookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
1755 JSProperty **propp)
1756 {
1757 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1758 if (!proto)
1759 return js_LookupProperty(cx, obj, id, objp, propp);
1760 return OBJ_LOOKUP_PROPERTY(cx, proto, id, objp, propp);
1761 }
1763 static JSBool
1764 with_GetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
1765 {
1766 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1767 if (!proto)
1768 return js_GetProperty(cx, obj, id, vp);
1769 return OBJ_GET_PROPERTY(cx, proto, id, vp);
1770 }
1772 static JSBool
1773 with_SetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
1774 {
1775 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1776 if (!proto)
1777 return js_SetProperty(cx, obj, id, vp);
1778 return OBJ_SET_PROPERTY(cx, proto, id, vp);
1779 }
1781 static JSBool
1782 with_GetAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
1783 uintN *attrsp)
1784 {
1785 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1786 if (!proto)
1787 return js_GetAttributes(cx, obj, id, prop, attrsp);
1788 return OBJ_GET_ATTRIBUTES(cx, proto, id, prop, attrsp);
1789 }
1791 static JSBool
1792 with_SetAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
1793 uintN *attrsp)
1794 {
1795 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1796 if (!proto)
1797 return js_SetAttributes(cx, obj, id, prop, attrsp);
1798 return OBJ_SET_ATTRIBUTES(cx, proto, id, prop, attrsp);
1799 }
1801 static JSBool
1802 with_DeleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *rval)
1803 {
1804 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1805 if (!proto)
1806 return js_DeleteProperty(cx, obj, id, rval);
1807 return OBJ_DELETE_PROPERTY(cx, proto, id, rval);
1808 }
1810 static JSBool
1811 with_DefaultValue(JSContext *cx, JSObject *obj, JSType hint, jsval *vp)
1812 {
1813 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1814 if (!proto)
1815 return js_DefaultValue(cx, obj, hint, vp);
1816 return OBJ_DEFAULT_VALUE(cx, proto, hint, vp);
1817 }
1819 static JSBool
1820 with_Enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
1821 jsval *statep, jsid *idp)
1822 {
1823 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1824 if (!proto)
1825 return js_Enumerate(cx, obj, enum_op, statep, idp);
1826 return OBJ_ENUMERATE(cx, proto, enum_op, statep, idp);
1827 }
1829 static JSBool
1830 with_CheckAccess(JSContext *cx, JSObject *obj, jsid id, JSAccessMode mode,
1831 jsval *vp, uintN *attrsp)
1832 {
1833 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1834 if (!proto)
1835 return js_CheckAccess(cx, obj, id, mode, vp, attrsp);
1836 return OBJ_CHECK_ACCESS(cx, proto, id, mode, vp, attrsp);
1837 }
1839 static JSObject *
1840 with_ThisObject(JSContext *cx, JSObject *obj)
1841 {
1842 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1843 if (!proto)
1844 return obj;
1845 return OBJ_THIS_OBJECT(cx, proto);
1846 }
1848 JS_FRIEND_DATA(JSObjectOps) js_WithObjectOps = {
1849 js_NewObjectMap, js_DestroyObjectMap,
1850 with_LookupProperty, js_DefineProperty,
1851 with_GetProperty, with_SetProperty,
1852 with_GetAttributes, with_SetAttributes,
1853 with_DeleteProperty, with_DefaultValue,
1854 with_Enumerate, with_CheckAccess,
1855 with_ThisObject, NATIVE_DROP_PROPERTY,
1856 NULL, NULL,
1857 NULL, NULL,
1858 js_SetProtoOrParent, js_SetProtoOrParent,
1859 js_Mark, js_Clear,
1860 NULL, NULL
1861 };
1863 static JSObjectOps *
1864 with_getObjectOps(JSContext *cx, JSClass *clasp)
1865 {
1866 return &js_WithObjectOps;
1867 }
1869 JSClass js_WithClass = {
1870 "With",
1871 JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1),
1872 JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
1873 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
1874 with_getObjectOps,
1875 0,0,0,0,0,0,0
1876 };
1878 JSObject *
1879 js_NewWithObject(JSContext *cx, JSObject *proto, JSObject *parent, jsint depth)
1880 {
1881 JSObject *obj;
1883 obj = js_NewObject(cx, &js_WithClass, proto, parent);
1884 if (!obj)
1885 return NULL;
1886 obj->slots[JSSLOT_PRIVATE] = PRIVATE_TO_JSVAL(cx->fp);
1887 OBJ_SET_BLOCK_DEPTH(cx, obj, depth);
1888 return obj;
1889 }
1891 #if JS_HAS_OBJ_PROTO_PROP
1892 static JSBool
1893 With(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1894 {
1895 JSObject *parent, *proto;
1896 jsval v;
1898 if (!JS_ReportErrorFlagsAndNumber(cx,
1899 JSREPORT_WARNING | JSREPORT_STRICT,
1900 js_GetErrorMessage, NULL,
1901 JSMSG_DEPRECATED_USAGE,
1902 js_WithClass.name)) {
1903 return JS_FALSE;
1904 }
1906 if (!(cx->fp->flags & JSFRAME_CONSTRUCTING)) {
1907 obj = js_NewWithObject(cx, NULL, NULL, -1);
1908 if (!obj)
1909 return JS_FALSE;
1910 *rval = OBJECT_TO_JSVAL(obj);
1911 }
1913 parent = cx->fp->scopeChain;
1914 if (argc > 0) {
1915 if (!js_ValueToObject(cx, argv[0], &proto))
1916 return JS_FALSE;
1917 v = OBJECT_TO_JSVAL(proto);
1918 if (!obj_setSlot(cx, obj, INT_TO_JSVAL(JSSLOT_PROTO), &v))
1919 return JS_FALSE;
1920 if (argc > 1) {
1921 if (!js_ValueToObject(cx, argv[1], &parent))
1922 return JS_FALSE;
1923 }
1924 }
1925 v = OBJECT_TO_JSVAL(parent);
1926 return obj_setSlot(cx, obj, INT_TO_JSVAL(JSSLOT_PARENT), &v);
1927 }
1928 #endif
1930 JSObject *
1931 js_InitObjectClass(JSContext *cx, JSObject *obj)
1932 {
1933 JSObject *proto;
1934 jsval eval;
1936 #if JS_HAS_SHARP_VARS
1937 JS_ASSERT(sizeof(jsatomid) * JS_BITS_PER_BYTE >= ATOM_INDEX_LIMIT_LOG2 + 1);
1938 #endif
1940 proto = JS_InitClass(cx, obj, NULL, &js_ObjectClass, Object, 1,
1941 object_props, object_methods, NULL, NULL);
1942 if (!proto)
1943 return NULL;
1945 #if JS_HAS_OBJ_PROTO_PROP
1946 if (!JS_InitClass(cx, obj, NULL, &js_WithClass, With, 0,
1947 NULL, NULL, NULL, NULL)) {
1948 return NULL;
1949 }
1950 #endif
1952 /* ECMA (15.1.2.1) says 'eval' is also a property of the global object. */
1953 if (!OBJ_GET_PROPERTY(cx, proto,
1954 ATOM_TO_JSID(cx->runtime->atomState.evalAtom),
1955 &eval)) {
1956 return NULL;
1957 }
1958 if (!OBJ_DEFINE_PROPERTY(cx, obj,
1959 ATOM_TO_JSID(cx->runtime->atomState.evalAtom),
1960 eval, NULL, NULL, 0, NULL)) {
1961 return NULL;
1962 }
1964 return proto;
1965 }
1967 void
1968 js_InitObjectMap(JSObjectMap *map, jsrefcount nrefs, JSObjectOps *ops,
1969 JSClass *clasp)
1970 {
1971 map->nrefs = nrefs;
1972 map->ops = ops;
1973 map->nslots = JS_INITIAL_NSLOTS;
1974 map->freeslot = JSSLOT_FREE(clasp);
1975 }
1977 JSObjectMap *
1978 js_NewObjectMap(JSContext *cx, jsrefcount nrefs, JSObjectOps *ops,
1979 JSClass *clasp, JSObject *obj)
1980 {
1981 return (JSObjectMap *) js_NewScope(cx, nrefs, ops, clasp, obj);
1982 }
1984 void
1985 js_DestroyObjectMap(JSContext *cx, JSObjectMap *map)
1986 {
1987 js_DestroyScope(cx, (JSScope *)map);
1988 }
1990 JSObjectMap *
1991 js_HoldObjectMap(JSContext *cx, JSObjectMap *map)
1992 {
1993 JS_ASSERT(map->nrefs >= 0);
1994 JS_ATOMIC_INCREMENT(&map->nrefs);
1995 return map;
1996 }
1998 JSObjectMap *
1999 js_DropObjectMap(JSContext *cx, JSObjectMap *map, JSObject *obj)
2000 {
2001 JS_ASSERT(map->nrefs > 0);
2002 JS_ATOMIC_DECREMENT(&map->nrefs);
2003 if (map->nrefs == 0) {
2004 map->ops->destroyObjectMap(cx, map);
2005 return NULL;
2006 }
2007 if (MAP_IS_NATIVE(map) && ((JSScope *)map)->object == obj)
2008 ((JSScope *)map)->object = NULL;
2009 return map;
2010 }
2012 static JSBool
2013 GetClassPrototype(JSContext *cx, JSObject *scope, const char *name,
2014 JSObject **protop);
2016 static jsval *
2017 AllocSlots(JSContext *cx, jsval *slots, uint32 nslots)
2018 {
2019 size_t nbytes, obytes, minbytes;
2020 uint32 i, oslots;
2021 jsval *newslots;
2023 nbytes = (nslots + 1) * sizeof(jsval);
2024 if (slots) {
2025 oslots = slots[-1];
2026 obytes = (oslots + 1) * sizeof(jsval);
2027 } else {
2028 oslots = 0;
2029 obytes = 0;
2030 }
2032 if (nbytes <= GC_NBYTES_MAX) {
2033 newslots = (jsval *) js_NewGCThing(cx, GCX_PRIVATE, nbytes);
2034 } else {
2035 newslots = (jsval *)
2036 JS_realloc(cx,
2037 (obytes <= GC_NBYTES_MAX) ? NULL : slots - 1,
2038 nbytes);
2039 }
2040 if (!newslots)
2041 return NULL;
2043 if (obytes != 0) {
2044 /* If either nbytes or obytes fit in a GC-thing, we must copy. */
2045 minbytes = JS_MIN(nbytes, obytes);
2046 if (minbytes <= GC_NBYTES_MAX)
2047 memcpy(newslots + 1, slots, minbytes - sizeof(jsval));
2049 /* If nbytes are in a GC-thing but obytes aren't, free obytes. */
2050 if (nbytes <= GC_NBYTES_MAX && obytes > GC_NBYTES_MAX)
2051 JS_free(cx, slots - 1);
2053 /* If we're extending an allocation, initialize free slots. */
2054 if (nslots > oslots) {
2055 for (i = 1 + oslots; i <= nslots; i++)
2056 newslots[i] = JSVAL_VOID;
2057 }
2058 }
2060 newslots[0] = nslots;
2061 return ++newslots;
2062 }
2064 static void
2065 FreeSlots(JSContext *cx, jsval *slots)
2066 {
2067 size_t nbytes;
2069 /*
2070 * NB: We count on smaller GC-things being finalized before larger things
2071 * that become garbage during the same GC. Without this assumption, we
2072 * couldn't load slots[-1] here without possibly loading a gcFreeList link
2073 * (see struct JSGCThing in jsgc.h).
2074 */
2075 nbytes = (slots[-1] + 1) * sizeof(jsval);
2076 if (nbytes > GC_NBYTES_MAX)
2077 JS_free(cx, slots - 1);
2078 }
2080 JSObject *
2081 js_NewObject(JSContext *cx, JSClass *clasp, JSObject *proto, JSObject *parent)
2082 {
2083 JSObject *obj;
2084 JSObjectOps *ops;
2085 JSObjectMap *map;
2086 JSClass *protoclasp;
2087 uint32 nslots, i;
2088 jsval *newslots;
2089 JSTempValueRooter tvr;
2091 /* Bootstrap the ur-object, and make it the default prototype object. */
2092 if (!proto) {
2093 if (!GetClassPrototype(cx, parent, clasp->name, &proto))
2094 return NULL;
2095 if (!proto && !GetClassPrototype(cx, parent, js_Object_str, &proto))
2096 return NULL;
2097 }
2099 /* Always call the class's getObjectOps hook if it has one. */
2100 ops = clasp->getObjectOps
2101 ? clasp->getObjectOps(cx, clasp)
2102 : &js_ObjectOps;
2104 /*
2105 * Allocate a zeroed object from the GC heap. Do this *after* any other
2106 * GC-thing allocations under GetClassPrototype or clasp->getObjectOps,
2107 * to avoid displacing the newborn root for obj.
2108 */
2109 obj = (JSObject *) js_NewGCThing(cx, GCX_OBJECT, sizeof(JSObject));
2110 if (!obj)
2111 return NULL;
2113 /*
2114 * Root obj to prevent it from being killed.
2115 * AllocSlots can trigger a finalizer from a last-ditch GC calling
2116 * JS_ClearNewbornRoots. There's also the possibilty of things
2117 * happening under the objectHook call-out below.
2118 */
2119 JS_PUSH_SINGLE_TEMP_ROOT(cx, OBJECT_TO_JSVAL(obj), &tvr);
2121 /*
2122 * Share proto's map only if it has the same JSObjectOps, and only if
2123 * proto's class has the same private and reserved slots as obj's map
2124 * and class have. We assume that if prototype and object are of the
2125 * same class, they always have the same number of computed reserved
2126 * slots (returned via clasp->reserveSlots); otherwise, prototype and
2127 * object classes must have the same (null or not) reserveSlots hook.
2128 */
2129 if (proto &&
2130 (map = proto->map)->ops == ops &&
2131 ((protoclasp = OBJ_GET_CLASS(cx, proto)) == clasp ||
2132 (!((protoclasp->flags ^ clasp->flags) &
2133 (JSCLASS_HAS_PRIVATE |
2134 (JSCLASS_RESERVED_SLOTS_MASK << JSCLASS_RESERVED_SLOTS_SHIFT))) &&
2135 protoclasp->reserveSlots == clasp->reserveSlots)))
2136 {
2137 /*
2138 * Default parent to the parent of the prototype, which was set from
2139 * the parent of the prototype's constructor.
2140 */
2141 if (!parent)
2142 parent = OBJ_GET_PARENT(cx, proto);
2144 /* Share the given prototype's map. */
2145 obj->map = js_HoldObjectMap(cx, map);
2147 /* Ensure that obj starts with the minimum slots for clasp. */
2148 nslots = JS_INITIAL_NSLOTS;
2149 } else {
2150 /* Leave parent alone. Allocate a new map for obj. */
2151 map = ops->newObjectMap(cx, 1, ops, clasp, obj);
2152 if (!map)
2153 goto bad;
2154 obj->map = map;
2156 /* Let ops->newObjectMap set nslots so as to reserve slots. */
2157 nslots = map->nslots;
2158 }
2160 /* Allocate a slots vector, with a -1'st element telling its length. */
2161 newslots = AllocSlots(cx, NULL, nslots);
2162 if (!newslots) {
2163 js_DropObjectMap(cx, obj->map, obj);
2164 obj->map = NULL;
2165 goto bad;
2166 }
2168 /* Set the proto, parent, and class properties. */
2169 newslots[JSSLOT_PROTO] = OBJECT_TO_JSVAL(proto);
2170 newslots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(parent);
2171 newslots[JSSLOT_CLASS] = PRIVATE_TO_JSVAL(clasp);
2173 /* Clear above JSSLOT_CLASS so the GC doesn't load uninitialized memory. */
2174 for (i = JSSLOT_CLASS + 1; i < nslots; i++)
2175 newslots[i] = JSVAL_VOID;
2177 /* Store newslots after initializing all of 'em, just in case. */
2178 obj->slots = newslots;
2180 if (cx->runtime->objectHook) {
2181 JS_KEEP_ATOMS(cx->runtime);
2182 cx->runtime->objectHook(cx, obj, JS_TRUE, cx->runtime->objectHookData);
2183 JS_UNKEEP_ATOMS(cx->runtime);
2184 }
2186 out:
2187 JS_POP_TEMP_ROOT(cx, &tvr);
2188 cx->newborn[GCX_OBJECT] = (JSGCThing *) obj;
2189 return obj;
2191 bad:
2192 obj = NULL;
2193 goto out;
2194 }
2196 JSBool
2197 js_FindConstructor(JSContext *cx, JSObject *start, const char *name, jsval *vp)
2198 {
2199 JSAtom *atom;
2200 JSObject *obj, *pobj;
2201 JSProperty *prop;
2202 JSScopeProperty *sprop;
2204 atom = js_Atomize(cx, name, strlen(name), 0);
2205 if (!atom)
2206 return JS_FALSE;
2208 if (start || (cx->fp && (start = cx->fp->scopeChain) != NULL)) {
2209 /* Find the topmost object in the scope chain. */
2210 do {
2211 obj = start;
2212 start = OBJ_GET_PARENT(cx, obj);
2213 } while (start);
2214 } else {
2215 obj = cx->globalObject;
2216 if (!obj) {
2217 *vp = JSVAL_VOID;
2218 return JS_TRUE;
2219 }
2220 }
2222 JS_ASSERT(OBJ_IS_NATIVE(obj));
2223 if (!js_LookupPropertyWithFlags(cx, obj, ATOM_TO_JSID(atom),
2224 JSRESOLVE_CLASSNAME, &pobj, &prop)) {
2225 return JS_FALSE;
2226 }
2227 if (!prop) {
2228 *vp = JSVAL_VOID;
2229 return JS_TRUE;
2230 }
2232 JS_ASSERT(OBJ_IS_NATIVE(pobj));
2233 sprop = (JSScopeProperty *) prop;
2234 JS_ASSERT(SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(pobj)));
2235 *vp = OBJ_GET_SLOT(cx, pobj, sprop->slot);
2236 OBJ_DROP_PROPERTY(cx, pobj, prop);
2237 return JS_TRUE;
2238 }
2240 JSObject *
2241 js_ConstructObject(JSContext *cx, JSClass *clasp, JSObject *proto,
2242 JSObject *parent, uintN argc, jsval *argv)
2243 {
2244 jsval cval, rval;
2245 JSTempValueRooter argtvr, tvr;
2246 JSObject *obj, *ctor;
2248 JS_PUSH_TEMP_ROOT(cx, argc, argv, &argtvr);
2250 if (!js_FindConstructor(cx, parent, clasp->name, &cval)) {
2251 JS_POP_TEMP_ROOT(cx, &argtvr);
2252 return NULL;
2253 }
2254 if (JSVAL_IS_PRIMITIVE(cval)) {
2255 js_ReportIsNotFunction(cx, &cval, JSV2F_CONSTRUCT | JSV2F_SEARCH_STACK);
2256 JS_POP_TEMP_ROOT(cx, &argtvr);
2257 return NULL;
2258 }
2260 /*
2261 * Protect cval in case a crazy getter for .prototype uproots it. After
2262 * this point, all control flow must exit through label out with obj set.
2263 */
2264 JS_PUSH_SINGLE_TEMP_ROOT(cx, cval, &tvr);
2266 /*
2267 * If proto or parent are NULL, set them to Constructor.prototype and/or
2268 * Constructor.__parent__, just like JSOP_NEW does.
2269 */
2270 ctor = JSVAL_TO_OBJECT(cval);
2271 if (!parent)
2272 parent = OBJ_GET_PARENT(cx, ctor);
2273 if (!proto) {
2274 if (!OBJ_GET_PROPERTY(cx, ctor,
2275 ATOM_TO_JSID(cx->runtime->atomState
2276 .classPrototypeAtom),
2277 &rval)) {
2278 obj = NULL;
2279 goto out;
2280 }
2281 if (JSVAL_IS_OBJECT(rval))
2282 proto = JSVAL_TO_OBJECT(rval);
2283 }
2285 obj = js_NewObject(cx, clasp, proto, parent);
2286 if (!obj)
2287 goto out;
2289 if (!js_InternalConstruct(cx, obj, cval, argc, argv, &rval))
2290 goto bad;
2292 if (JSVAL_IS_PRIMITIVE(rval))
2293 goto out;
2294 obj = JSVAL_TO_OBJECT(rval);
2296 /*
2297 * If the given class has both the JSCLASS_HAS_PRIVATE and the
2298 * JSCLASS_CONSTRUCT_PROTOTYPE flags, then the class should have its private
2299 * data set. If it doesn't, then it means the constructor was replaced, and
2300 * we should throw a typerr.
2301 */
2302 if (OBJ_GET_CLASS(cx, obj) != clasp ||
2303 (!(~clasp->flags & (JSCLASS_HAS_PRIVATE |
2304 JSCLASS_CONSTRUCT_PROTOTYPE)) &&
2305 !JS_GetPrivate(cx, obj))) {
2306 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2307 JSMSG_WRONG_CONSTRUCTOR, clasp->name);
2308 goto bad;
2309 }
2311 out:
2312 JS_POP_TEMP_ROOT(cx, &tvr);
2313 JS_POP_TEMP_ROOT(cx, &argtvr);
2314 return obj;
2316 bad:
2317 cx->newborn[GCX_OBJECT] = NULL;
2318 obj = NULL;
2319 goto out;
2320 }
2322 void
2323 js_FinalizeObject(JSContext *cx, JSObject *obj)
2324 {
2325 JSObjectMap *map;
2327 /* Cope with stillborn objects that have no map. */
2328 map = obj->map;
2329 if (!map)
2330 return;
2331 JS_ASSERT(obj->slots);
2333 if (cx->runtime->objectHook)
2334 cx->runtime->objectHook(cx, obj, JS_FALSE, cx->runtime->objectHookData);
2336 /* Remove all watchpoints with weak links to obj. */
2337 JS_ClearWatchPointsForObject(cx, obj);
2339 /*
2340 * Finalize obj first, in case it needs map and slots. Optimized to use
2341 * LOCKED_OBJ_GET_CLASS instead of OBJ_GET_CLASS, so we avoid "promoting"
2342 * obj's scope from lock-free to lock-full (see jslock.c:ClaimScope) when
2343 * we're called from the GC. Only the GC should call js_FinalizeObject,
2344 * and no other threads run JS (and possibly racing to update obj->slots)
2345 * while the GC is running.
2346 */
2347 LOCKED_OBJ_GET_CLASS(obj)->finalize(cx, obj);
2349 /* Drop map and free slots. */
2350 js_DropObjectMap(cx, map, obj);
2351 obj->map = NULL;
2352 FreeSlots(cx, obj->slots);
2353 obj->slots = NULL;
2354 }
2356 /* XXXbe if one adds props, deletes earlier props, adds more, the last added
2357 won't recycle the deleted props' slots. */
2358 JSBool
2359 js_AllocSlot(JSContext *cx, JSObject *obj, uint32 *slotp)
2360 {
2361 JSObjectMap *map;
2362 JSClass *clasp;
2363 uint32 nslots;
2364 jsval *newslots;
2366 map = obj->map;
2367 JS_ASSERT(!MAP_IS_NATIVE(map) || ((JSScope *)map)->object == obj);
2368 clasp = LOCKED_OBJ_GET_CLASS(obj);
2369 if (map->freeslot == JSSLOT_FREE(clasp)) {
2370 /* Adjust map->freeslot to include computed reserved slots, if any. */
2371 if (clasp->reserveSlots)
2372 map->freeslot += clasp->reserveSlots(cx, obj);
2373 }
2374 nslots = map->nslots;
2375 if (map->freeslot >= nslots) {
2376 nslots = map->freeslot;
2377 JS_ASSERT(nslots >= JS_INITIAL_NSLOTS);
2378 nslots += (nslots + 1) / 2;
2380 newslots = AllocSlots(cx, obj->slots, nslots);
2381 if (!newslots)
2382 return JS_FALSE;
2383 map->nslots = nslots;
2384 obj->slots = newslots;
2385 }
2387 #ifdef TOO_MUCH_GC
2388 obj->slots[map->freeslot] = JSVAL_VOID;
2389 #endif
2390 *slotp = map->freeslot++;
2391 return JS_TRUE;
2392 }
2394 void
2395 js_FreeSlot(JSContext *cx, JSObject *obj, uint32 slot)
2396 {
2397 JSObjectMap *map;
2398 uint32 nslots;
2399 jsval *newslots;
2401 OBJ_CHECK_SLOT(obj, slot);
2402 obj->slots[slot] = JSVAL_VOID;
2403 map = obj->map;
2404 JS_ASSERT(!MAP_IS_NATIVE(map) || ((JSScope *)map)->object == obj);
2405 if (map->freeslot == slot + 1)
2406 map->freeslot = slot;
2407 nslots = map->nslots;
2408 if (nslots > JS_INITIAL_NSLOTS && map->freeslot < nslots / 2) {
2409 nslots = map->freeslot;
2410 nslots += nslots / 2;
2411 if (nslots < JS_INITIAL_NSLOTS)
2412 nslots = JS_INITIAL_NSLOTS;
2414 newslots = AllocSlots(cx, obj->slots, nslots);
2415 if (!newslots)
2416 return;
2417 map->nslots = nslots;
2418 obj->slots = newslots;
2419 }
2420 }
2422 #if JS_BUG_EMPTY_INDEX_ZERO
2423 #define CHECK_FOR_EMPTY_INDEX(id) \
2424 JS_BEGIN_MACRO \
2425 if (JSSTRING_LENGTH(str_) == 0) \
2426 id = JSVAL_ZERO; \
2427 JS_END_MACRO
2428 #else
2429 #define CHECK_FOR_EMPTY_INDEX(id) /* nothing */
2430 #endif
2432 /* JSVAL_INT_MAX as a string */
2433 #define JSVAL_INT_MAX_STRING "1073741823"
2435 #define CHECK_FOR_STRING_INDEX(id) \
2436 JS_BEGIN_MACRO \
2437 if (JSID_IS_ATOM(id)) { \
2438 JSAtom *atom_ = JSID_TO_ATOM(id); \
2439 JSString *str_ = ATOM_TO_STRING(atom_); \
2440 const jschar *cp_ = str_->chars; \
2441 JSBool negative_ = (*cp_ == '-'); \
2442 if (negative_) cp_++; \
2443 if (JS7_ISDEC(*cp_)) { \
2444 size_t n_ = str_->length - negative_; \
2445 if (n_ <= sizeof(JSVAL_INT_MAX_STRING) - 1) \
2446 id = CheckForStringIndex(id, cp_, cp_ + n_, negative_); \
2447 } else { \
2448 CHECK_FOR_EMPTY_INDEX(id); \
2449 } \
2450 } \
2451 JS_END_MACRO
2453 static jsid
2454 CheckForStringIndex(jsid id, const jschar *cp, const jschar *end,
2455 JSBool negative)
2456 {
2457 jsuint index = JS7_UNDEC(*cp++);
2458 jsuint oldIndex = 0;
2459 jsuint c = 0;
2461 if (index != 0) {
2462 while (JS7_ISDEC(*cp)) {
2463 oldIndex = index;
2464 c = JS7_UNDEC(*cp);
2465 index = 10 * index + c;
2466 cp++;
2467 }
2468 }
2469 if (cp == end &&
2470 (oldIndex < (JSVAL_INT_MAX / 10) ||
2471 (oldIndex == (JSVAL_INT_MAX / 10) &&
2472 c <= (JSVAL_INT_MAX % 10)))) {
2473 if (negative)
2474 index = 0 - index;
2475 id = INT_TO_JSID((jsint)index);
2476 }
2477 return id;
2478 }
2480 static JSBool
2481 HidePropertyName(JSContext *cx, jsid *idp)
2482 {
2483 jsid id;
2484 JSAtom *atom, *hidden;
2486 id = *idp;
2487 JS_ASSERT(JSID_IS_ATOM(id));
2489 atom = JSID_TO_ATOM(id);
2490 JS_ASSERT(!(atom->flags & ATOM_HIDDEN));
2491 JS_ASSERT(ATOM_IS_STRING(atom));
2493 hidden = js_AtomizeString(cx, ATOM_TO_STRING(atom), ATOM_HIDDEN);
2494 if (!hidden)
2495 return JS_FALSE;
2497 /*
2498 * Link hidden to unhidden atom to optimize call_enumerate -- this means
2499 * the GC must mark a hidden atom's unhidden counterpart (see js_MarkAtom
2500 * in jsgc.c). It overloads the entry.value member, which for unhidden
2501 * atoms may point to keyword information.
2502 */
2503 hidden->entry.value = atom;
2504 *idp = ATOM_TO_JSID(hidden);
2505 return JS_TRUE;
2506 }
2508 JSScopeProperty *
2509 js_AddHiddenProperty(JSContext *cx, JSObject *obj, jsid id,
2510 JSPropertyOp getter, JSPropertyOp setter, uint32 slot,
2511 uintN attrs, uintN flags, intN shortid)
2512 {
2513 if (!HidePropertyName(cx, &id))
2514 return NULL;
2516 flags |= SPROP_IS_HIDDEN;
2517 return js_AddNativeProperty(cx, obj, id, getter, setter, slot, attrs,
2518 flags, shortid);
2519 }
2521 JSBool
2522 js_LookupHiddenProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
2523 JSProperty **propp)
2524 {
2525 return HidePropertyName(cx, &id) &&
2526 js_LookupProperty(cx, obj, id, objp, propp);
2527 }
2529 JSScopeProperty *
2530 js_AddNativeProperty(JSContext *cx, JSObject *obj, jsid id,
2531 JSPropertyOp getter, JSPropertyOp setter, uint32 slot,
2532 uintN attrs, uintN flags, intN shortid)
2533 {
2534 JSScope *scope;
2535 JSScopeProperty *sprop;
2537 JS_LOCK_OBJ(cx, obj);
2538 scope = js_GetMutableScope(cx, obj);
2539 if (!scope) {
2540 sprop = NULL;
2541 } else {
2542 /*
2543 * Handle old bug that took empty string as zero index. Also convert
2544 * string indices to integers if appropriate.
2545 */
2546 CHECK_FOR_STRING_INDEX(id);
2547 sprop = js_AddScopeProperty(cx, scope, id, getter, setter, slot, attrs,
2548 flags, shortid);
2549 }
2550 JS_UNLOCK_OBJ(cx, obj);
2551 return sprop;
2552 }
2554 JSScopeProperty *
2555 js_ChangeNativePropertyAttrs(JSContext *cx, JSObject *obj,
2556 JSScopeProperty *sprop, uintN attrs, uintN mask,
2557 JSPropertyOp getter, JSPropertyOp setter)
2558 {
2559 JSScope *scope;
2561 JS_LOCK_OBJ(cx, obj);
2562 scope = js_GetMutableScope(cx, obj);
2563 if (!scope) {
2564 sprop = NULL;
2565 } else {
2566 sprop = js_ChangeScopePropertyAttrs(cx, scope, sprop, attrs, mask,
2567 getter, setter);
2568 if (sprop) {
2569 PROPERTY_CACHE_FILL(&cx->runtime->propertyCache, obj, sprop->id,
2570 sprop);
2571 }
2572 }
2573 JS_UNLOCK_OBJ(cx, obj);
2574 return sprop;
2575 }
2577 JSBool
2578 js_DefineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value,
2579 JSPropertyOp getter, JSPropertyOp setter, uintN attrs,
2580 JSProperty **propp)
2581 {
2582 return js_DefineNativeProperty(cx, obj, id, value, getter, setter, attrs,
2583 0, 0, propp);
2584 }
2586 /*
2587 * Backward compatibility requires allowing addProperty hooks to mutate the
2588 * nominal initial value of a slot-full property, while GC safety wants that
2589 * value to be stored before the call-out through the hook. Optimize to do
2590 * both while saving cycles for classes that stub their addProperty hook.
2591 */
2592 #define ADD_PROPERTY_HELPER(cx,clasp,obj,scope,sprop,vp,cleanup) \
2593 JS_BEGIN_MACRO \
2594 if ((clasp)->addProperty != JS_PropertyStub) { \
2595 jsval nominal_ = *(vp); \
2596 if (!(clasp)->addProperty(cx, obj, SPROP_USERID(sprop), vp)) { \
2597 cleanup; \
2598 } \
2599 if (*(vp) != nominal_) { \
2600 if (SPROP_HAS_VALID_SLOT(sprop, scope)) \
2601 LOCKED_OBJ_SET_SLOT(obj, (sprop)->slot, *(vp)); \
2602 } \
2603 } \
2604 JS_END_MACRO
2606 JSBool
2607 js_DefineNativeProperty(JSContext *cx, JSObject *obj, jsid id, jsval value,
2608 JSPropertyOp getter, JSPropertyOp setter, uintN attrs,
2609 uintN flags, intN shortid, JSProperty **propp)
2610 {
2611 JSClass *clasp;
2612 JSScope *scope;
2613 JSProperty *prop;
2614 JSScopeProperty *sprop;
2616 /*
2617 * Handle old bug that took empty string as zero index. Also convert
2618 * string indices to integers if appropriate.
2619 */
2620 CHECK_FOR_STRING_INDEX(id);
2622 #if JS_HAS_GETTER_SETTER
2623 /*
2624 * If defining a getter or setter, we must check for its counterpart and
2625 * update the attributes and property ops. A getter or setter is really
2626 * only half of a property.
2627 */
2628 if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) {
2629 JSObject *pobj;
2631 /*
2632 * If JS_THREADSAFE and id is found, js_LookupProperty returns with
2633 * sprop non-null and pobj locked. If pobj == obj, the property is
2634 * already in obj and obj has its own (mutable) scope. So if we are
2635 * defining a getter whose setter was already defined, or vice versa,
2636 * finish the job via js_ChangeScopePropertyAttributes, and refresh
2637 * the property cache line for (obj, id) to map sprop.
2638 */
2639 if (!js_LookupProperty(cx, obj, id, &pobj, &prop))
2640 return JS_FALSE;
2641 sprop = (JSScopeProperty *) prop;
2642 if (sprop &&
2643 pobj == obj &&
2644 (sprop->attrs & (JSPROP_GETTER | JSPROP_SETTER))) {
2645 sprop = js_ChangeScopePropertyAttrs(cx, OBJ_SCOPE(obj), sprop,
2646 attrs, sprop->attrs,
2647 (attrs & JSPROP_GETTER)
2648 ? getter
2649 : sprop->getter,
2650 (attrs & JSPROP_SETTER)
2651 ? setter
2652 : sprop->setter);
2654 /* NB: obj == pobj, so we can share unlock code at the bottom. */
2655 if (!sprop)
2656 goto bad;
2657 goto out;
2658 }
2660 if (prop) {
2661 /* NB: call OBJ_DROP_PROPERTY, as pobj might not be native. */
2662 OBJ_DROP_PROPERTY(cx, pobj, prop);
2663 prop = NULL;
2664 }
2665 }
2666 #endif /* JS_HAS_GETTER_SETTER */
2668 /* Lock if object locking is required by this implementation. */
2669 JS_LOCK_OBJ(cx, obj);
2671 /* Use the object's class getter and setter by default. */
2672 clasp = LOCKED_OBJ_GET_CLASS(obj);
2673 if (!getter)
2674 getter = clasp->getProperty;
2675 if (!setter)
2676 setter = clasp->setProperty;
2678 /* Get obj's own scope if it has one, or create a new one for obj. */
2679 scope = js_GetMutableScope(cx, obj);
2680 if (!scope)
2681 goto bad;
2683 /* Add the property to scope, or replace an existing one of the same id. */
2684 if (clasp->flags & JSCLASS_SHARE_ALL_PROPERTIES)
2685 attrs |= JSPROP_SHARED;
2686 sprop = js_AddScopeProperty(cx, scope, id, getter, setter,
2687 SPROP_INVALID_SLOT, attrs, flags, shortid);
2688 if (!sprop)
2689 goto bad;
2691 /* Store value before calling addProperty, in case the latter GC's. */
2692 if (SPROP_HAS_VALID_SLOT(sprop, scope))
2693 LOCKED_OBJ_SET_SLOT(obj, sprop->slot, value);
2695 /* XXXbe called with lock held */
2696 ADD_PROPERTY_HELPER(cx, clasp, obj, scope, sprop, &value,
2697 js_RemoveScopeProperty(cx, scope, id);
2698 goto bad);
2700 #if JS_HAS_GETTER_SETTER
2701 out:
2702 #endif
2703 PROPERTY_CACHE_FILL(&cx->runtime->propertyCache, obj, id, sprop);
2704 if (propp)
2705 *propp = (JSProperty *) sprop;
2706 else
2707 JS_UNLOCK_OBJ(cx, obj);
2708 return JS_TRUE;
2710 bad:
2711 JS_UNLOCK_OBJ(cx, obj);
2712 return JS_FALSE;
2713 }
2715 /*
2716 * Given pc pointing after a property accessing bytecode, return true if the
2717 * access is a "object-detecting" in the sense used by web pages, e.g., when
2718 * checking whether document.all is defined.
2719 */
2720 static JSBool
2721 Detecting(JSContext *cx, jsbytecode *pc)
2722 {
2723 JSScript *script;
2724 jsbytecode *endpc;
2725 JSOp op;
2726 JSAtom *atom;
2728 if (!cx->fp)
2729 return JS_FALSE;
2730 script = cx->fp->script;
2731 for (endpc = script->code + script->length; pc < endpc; pc++) {
2732 /* General case: a branch or equality op follows the access. */
2733 op = (JSOp) *pc;
2734 if (js_CodeSpec[op].format & JOF_DETECTING)
2735 return JS_TRUE;
2737 /*
2738 * Special case #1: handle (document.all == null). Don't sweat about
2739 * JS1.2's revision of the equality operators here.
2740 */
2741 if (op == JSOP_NULL) {
2742 if (++pc < endpc)
2743 return *pc == JSOP_EQ || *pc == JSOP_NE;
2744 break;
2745 }
2747 /*
2748 * Special case #2: handle (document.all == undefined). Don't worry
2749 * about someone redefining undefined, which was added by Edition 3,
2750 * so was read/write for backward compatibility.
2751 */
2752 if (op == JSOP_NAME) {
2753 atom = GET_ATOM(cx, script, pc);
2754 if (atom == cx->runtime->atomState.typeAtoms[JSTYPE_VOID] &&
2755 (pc += js_CodeSpec[op].length) < endpc) {
2756 op = (JSOp) *pc;
2757 return op == JSOP_EQ || op == JSOP_NE ||
2758 op == JSOP_NEW_EQ || op == JSOP_NEW_NE;
2759 }
2760 break;
2761 }
2763 /* At this point, anything but grouping means we're not detecting. */
2764 if (op != JSOP_GROUP)
2765 break;
2766 }
2767 return JS_FALSE;
2768 }
2770 JS_FRIEND_API(JSBool)
2771 js_LookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
2772 JSProperty **propp)
2773 {
2774 return js_LookupPropertyWithFlags(cx, obj, id, 0, objp, propp);
2775 }
2777 JSBool
2778 js_LookupPropertyWithFlags(JSContext *cx, JSObject *obj, jsid id, uintN flags,
2779 JSObject **objp, JSProperty **propp)
2780 {
2781 JSObject *start, *obj2, *proto;
2782 JSScope *scope;
2783 JSScopeProperty *sprop;
2784 JSClass *clasp;
2785 JSResolveOp resolve;
2786 JSResolvingKey key;
2787 JSResolvingEntry *entry;
2788 uint32 generation;
2789 JSNewResolveOp newresolve;
2790 jsbytecode *pc;
2791 const JSCodeSpec *cs;
2792 uint32 format;
2793 JSBool ok;
2795 /*
2796 * Handle old bug that took empty string as zero index. Also convert
2797 * string indices to integers if appropriate.
2798 */
2799 CHECK_FOR_STRING_INDEX(id);
2801 /* Search scopes starting with obj and following the prototype link. */
2802 start = obj;
2803 for (;;) {
2804 JS_LOCK_OBJ(cx, obj);
2805 scope = OBJ_SCOPE(obj);
2806 if (scope->object == obj) {
2807 sprop = SCOPE_GET_PROPERTY(scope, id);
2808 } else {
2809 /* Shared prototype scope: try resolve before lookup. */
2810 sprop = NULL;
2811 }
2813 /* Try obj's class resolve hook if id was not found in obj's scope. */
2814 if (!sprop) {
2815 clasp = LOCKED_OBJ_GET_CLASS(obj);
2816 resolve = clasp->resolve;
2817 if (resolve != JS_ResolveStub) {
2818 /* Avoid recursion on (obj, id) already being resolved on cx. */
2819 key.obj = obj;
2820 key.id = id;
2822 /*
2823 * Once we have successfully added an entry for (obj, key) to
2824 * cx->resolvingTable, control must go through cleanup: before
2825 * returning. But note that JS_DHASH_ADD may find an existing
2826 * entry, in which case we bail to suppress runaway recursion.
2827 */
2828 if (!js_StartResolving(cx, &key, JSRESFLAG_LOOKUP, &entry)) {
2829 JS_UNLOCK_OBJ(cx, obj);
2830 return JS_FALSE;
2831 }
2832 if (!entry) {
2833 /* Already resolving id in obj -- dampen recursion. */
2834 JS_UNLOCK_OBJ(cx, obj);
2835 goto out;
2836 }
2837 generation = cx->resolvingTable->generation;
2839 /* Null *propp here so we can test it at cleanup: safely. */
2840 *propp = NULL;
2842 if (clasp->flags & JSCLASS_NEW_RESOLVE) {
2843 newresolve = (JSNewResolveOp)resolve;
2844 if (!(flags & JSRESOLVE_CLASSNAME) &&
2845 cx->fp &&
2846 (pc = cx->fp->pc)) {
2847 cs = &js_CodeSpec[*pc];
2848 format = cs->format;
2849 if ((format & JOF_MODEMASK) != JOF_NAME)
2850 flags |= JSRESOLVE_QUALIFIED;
2851 if ((format & JOF_ASSIGNING) ||
2852 (cx->fp->flags & JSFRAME_ASSIGNING)) {
2853 flags |= JSRESOLVE_ASSIGNING;
2854 } else {
2855 pc += cs->length;
2856 if (Detecting(cx, pc))
2857 flags |= JSRESOLVE_DETECTING;
2858 }
2859 if (format & JOF_DECLARING)
2860 flags |= JSRESOLVE_DECLARING;
2861 }
2862 obj2 = (clasp->flags & JSCLASS_NEW_RESOLVE_GETS_START)
2863 ? start
2864 : NULL;
2865 JS_UNLOCK_OBJ(cx, obj);
2867 /* Protect id and all atoms from a GC nested in resolve. */
2868 JS_KEEP_ATOMS(cx->runtime);
2869 ok = newresolve(cx, obj, ID_TO_VALUE(id), flags, &obj2);
2870 JS_UNKEEP_ATOMS(cx->runtime);
2871 if (!ok)
2872 goto cleanup;
2874 JS_LOCK_OBJ(cx, obj);
2875 if (obj2) {
2876 /* Resolved: juggle locks and lookup id again. */
2877 if (obj2 != obj) {
2878 JS_UNLOCK_OBJ(cx, obj);
2879 JS_LOCK_OBJ(cx, obj2);
2880 }
2881 scope = OBJ_SCOPE(obj2);
2882 if (!MAP_IS_NATIVE(&scope->map)) {
2883 /* Whoops, newresolve handed back a foreign obj2. */
2884 JS_ASSERT(obj2 != obj);
2885 JS_UNLOCK_OBJ(cx, obj2);
2886 ok = OBJ_LOOKUP_PROPERTY(cx, obj2, id, objp, propp);
2887 if (!ok || *propp)
2888 goto cleanup;
2889 JS_LOCK_OBJ(cx, obj2);
2890 } else {
2891 /*
2892 * Require that obj2 have its own scope now, as we
2893 * do for old-style resolve. If it doesn't, then
2894 * id was not truly resolved, and we'll find it in
2895 * the proto chain, or miss it if obj2's proto is
2896 * not on obj's proto chain. That last case is a
2897 * "too bad!" case.
2898 */
2899 if (scope->object == obj2)
2900 sprop = SCOPE_GET_PROPERTY(scope, id);
2901 }
2902 if (sprop) {
2903 JS_ASSERT(obj2 == scope->object);
2904 obj = obj2;
2905 } else if (obj2 != obj) {
2906 JS_UNLOCK_OBJ(cx, obj2);
2907 JS_LOCK_OBJ(cx, obj);
2908 }
2909 }
2910 } else {
2911 /*
2912 * Old resolve always requires id re-lookup if obj owns
2913 * its scope after resolve returns.
2914 */
2915 JS_UNLOCK_OBJ(cx, obj);
2916 ok = resolve(cx, obj, ID_TO_VALUE(id));
2917 if (!ok)
2918 goto cleanup;
2919 JS_LOCK_OBJ(cx, obj);
2920 scope = OBJ_SCOPE(obj);
2921 JS_ASSERT(MAP_IS_NATIVE(&scope->map));
2922 if (scope->object == obj)
2923 sprop = SCOPE_GET_PROPERTY(scope, id);
2924 }
2926 cleanup:
2927 js_StopResolving(cx, &key, JSRESFLAG_LOOKUP, entry, generation);
2928 if (!ok || *propp)
2929 return ok;
2930 }
2931 }
2933 if (sprop) {
2934 JS_ASSERT(OBJ_SCOPE(obj) == scope);
2935 *objp = scope->object; /* XXXbe hide in jsscope.[ch] */
2937 *propp = (JSProperty *) sprop;
2938 return JS_TRUE;
2939 }
2941 proto = LOCKED_OBJ_GET_PROTO(obj);
2942 JS_UNLOCK_OBJ(cx, obj);
2943 if (!proto)
2944 break;
2945 if (!OBJ_IS_NATIVE(proto))
2946 return OBJ_LOOKUP_PROPERTY(cx, proto, id, objp, propp);
2947 obj = proto;
2948 }
2950 out:
2951 *objp = NULL;
2952 *propp = NULL;
2953 return JS_TRUE;
2954 }
2956 JS_FRIEND_API(JSBool)
2957 js_FindProperty(JSContext *cx, jsid id, JSObject **objp, JSObject **pobjp,
2958 JSProperty **propp)
2959 {
2960 JSRuntime *rt;
2961 JSObject *obj, *pobj, *lastobj;
2962 JSScopeProperty *sprop;
2963 JSProperty *prop;
2965 rt = cx->runtime;
2966 obj = cx->fp->scopeChain;
2967 do {
2968 /* Try the property cache and return immediately on cache hit. */
2969 if (OBJ_IS_NATIVE(obj)) {
2970 JS_LOCK_OBJ(cx, obj);
2971 PROPERTY_CACHE_TEST(&rt->propertyCache, obj, id, sprop);
2972 if (sprop) {
2973 JS_ASSERT(OBJ_IS_NATIVE(obj));
2974 *objp = obj;
2975 *pobjp = obj;
2976 *propp = (JSProperty *) sprop;
2977 return JS_TRUE;
2978 }
2979 JS_UNLOCK_OBJ(cx, obj);
2980 }
2982 /* If cache miss, take the slow path. */
2983 if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &pobj, &prop))
2984 return JS_FALSE;
2985 if (prop) {
2986 if (OBJ_IS_NATIVE(pobj)) {
2987 sprop = (JSScopeProperty *) prop;
2988 PROPERTY_CACHE_FILL(&rt->propertyCache, pobj, id, sprop);
2989 }
2990 *objp = obj;
2991 *pobjp = pobj;
2992 *propp = prop;
2993 return JS_TRUE;
2994 }
2995 lastobj = obj;
2996 } while ((obj = OBJ_GET_PARENT(cx, obj)) != NULL);
2998 *objp = lastobj;
2999 *pobjp = NULL;
3000 *propp = NULL;
3001 return JS_TRUE;
3002 }
3004 JSObject *
3005 js_FindIdentifierBase(JSContext *cx, jsid id)
3006 {
3007 JSObject *obj, *pobj;
3008 JSProperty *prop;
3010 /*
3011 * Look for id's property along the "with" statement chain and the
3012 * statically-linked scope chain.
3013 */
3014 if (!js_FindProperty(cx, id, &obj, &pobj, &prop))
3015 return NULL;
3016 if (prop) {
3017 OBJ_DROP_PROPERTY(cx, pobj, prop);
3018 return obj;
3019 }
3021 /*
3022 * Use the top-level scope from the scope chain, which won't end in the
3023 * same scope as cx->globalObject for cross-context function calls.
3024 */
3025 JS_ASSERT(obj);
3027 /*
3028 * Property not found. Give a strict warning if binding an undeclared
3029 * top-level variable.
3030 */
3031 if (JS_HAS_STRICT_OPTION(cx)) {
3032 JSString *str = JSVAL_TO_STRING(ID_TO_VALUE(id));
3033 if (!JS_ReportErrorFlagsAndNumber(cx,
3034 JSREPORT_WARNING | JSREPORT_STRICT,
3035 js_GetErrorMessage, NULL,
3036 JSMSG_UNDECLARED_VAR,
3037 JS_GetStringBytes(str))) {
3038 return NULL;
3039 }
3040 }
3041 return obj;
3042 }
3044 JSBool
3045 js_GetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
3046 {
3047 JSObject *obj2;
3048 JSProperty *prop;
3049 JSScope *scope;
3050 JSScopeProperty *sprop;
3051 uint32 slot;
3053 /*
3054 * Handle old bug that took empty string as zero index. Also convert
3055 * string indices to integers if appropriate.
3056 */
3057 CHECK_FOR_STRING_INDEX(id);
3059 if (!js_LookupProperty(cx, obj, id, &obj2, &prop))
3060 return JS_FALSE;
3061 if (!prop) {
3062 jsval default_val;
3064 #if JS_BUG_NULL_INDEX_PROPS
3065 /* Indexed properties defaulted to null in old versions. */
3066 default_val = (JSID_IS_INT(id) && JSID_TO_INT(id) >= 0)
3067 ? JSVAL_NULL
3068 : JSVAL_VOID;
3069 #else
3070 default_val = JSVAL_VOID;
3071 #endif
3072 *vp = default_val;
3074 if (!OBJ_GET_CLASS(cx, obj)->getProperty(cx, obj, ID_TO_VALUE(id), vp))
3075 return JS_FALSE;
3077 /*
3078 * Give a strict warning if foo.bar is evaluated by a script for an
3079 * object foo with no property named 'bar'.
3080 */
3081 if (JS_HAS_STRICT_OPTION(cx) &&
3082 *vp == default_val &&
3083 cx->fp && cx->fp->pc &&
3084 (*cx->fp->pc == JSOP_GETPROP || *cx->fp->pc == JSOP_GETELEM))
3085 {
3086 jsbytecode *pc;
3087 JSString *str;
3089 /* Kludge to allow (typeof foo == "undefined") tests. */
3090 JS_ASSERT(cx->fp->script);
3091 pc = cx->fp->pc;
3092 pc += js_CodeSpec[*pc].length;
3093 if (Detecting(cx, pc))
3094 return JS_TRUE;
3096 /* Ok, bad undefined property reference: whine about it. */
3097 str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK,
3098 ID_TO_VALUE(id), NULL);
3099 if (!str ||
3100 !JS_ReportErrorFlagsAndNumber(cx,
3101 JSREPORT_WARNING|JSREPORT_STRICT,
3102 js_GetErrorMessage, NULL,
3103 JSMSG_UNDEFINED_PROP,
3104 JS_GetStringBytes(str))) {
3105 return JS_FALSE;
3106 }
3107 }
3108 return JS_TRUE;
3109 }
3111 if (!OBJ_IS_NATIVE(obj2)) {
3112 OBJ_DROP_PROPERTY(cx, obj2, prop);
3113 return OBJ_GET_PROPERTY(cx, obj2, id, vp);
3114 }
3116 /* Unlock obj2 before calling getter, relock after to avoid deadlock. */
3117 scope = OBJ_SCOPE(obj2);
3118 sprop = (JSScopeProperty *) prop;
3119 slot = sprop->slot;
3120 if (slot != SPROP_INVALID_SLOT) {
3121 JS_ASSERT(slot < obj2->map->freeslot);
3122 *vp = LOCKED_OBJ_GET_SLOT(obj2, slot);
3124 /* If sprop has a stub getter, we're done. */
3125 if (!sprop->getter)
3126 goto out;
3127 } else {
3128 *vp = JSVAL_VOID;
3129 }
3131 JS_UNLOCK_SCOPE(cx, scope);
3132 if (!SPROP_GET(cx, sprop, obj, obj2, vp))
3133 return JS_FALSE;
3134 JS_LOCK_SCOPE(cx, scope);
3136 if (SPROP_HAS_VALID_SLOT(sprop, scope)) {
3137 LOCKED_OBJ_SET_SLOT(obj2, slot, *vp);
3138 PROPERTY_CACHE_FILL(&cx->runtime->propertyCache, obj2, id, sprop);
3139 }
3141 out:
3142 JS_UNLOCK_SCOPE(cx, scope);
3143 return JS_TRUE;
3144 }
3146 JSBool
3147 js_SetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
3148 {
3149 JSObject *pobj;
3150 JSProperty *prop;
3151 JSScopeProperty *sprop;
3152 JSScope *scope;
3153 uintN attrs, flags;
3154 intN shortid;
3155 JSClass *clasp;
3156 JSPropertyOp getter, setter;
3157 jsval pval;
3158 uint32 slot;
3160 /*
3161 * Handle old bug that took empty string as zero index. Also convert
3162 * string indices to integers if appropriate.
3163 */
3164 CHECK_FOR_STRING_INDEX(id);
3166 if (!js_LookupProperty(cx, obj, id, &pobj, &prop))
3167 return JS_FALSE;
3169 if (prop && !OBJ_IS_NATIVE(pobj)) {
3170 OBJ_DROP_PROPERTY(cx, pobj, prop);
3171 prop = NULL;
3172 }
3173 sprop = (JSScopeProperty *) prop;
3175 /*
3176 * Now either sprop is null, meaning id was not found in obj or one of its
3177 * prototypes; or sprop is non-null, meaning id was found in pobj's scope.
3178 * If JS_THREADSAFE and sprop is non-null, then scope is locked, and sprop
3179 * is held: we must OBJ_DROP_PROPERTY or JS_UNLOCK_SCOPE before we return
3180 * (the two are equivalent for native objects, but we use JS_UNLOCK_SCOPE
3181 * because it is cheaper).
3182 */
3183 attrs = JSPROP_ENUMERATE;
3184 flags = 0;
3185 shortid = 0;
3186 clasp = OBJ_GET_CLASS(cx, obj);
3187 getter = clasp->getProperty;
3188 setter = clasp->setProperty;
3190 if (sprop) {
3191 /*
3192 * Set scope for use below. It was locked by js_LookupProperty, and
3193 * we know pobj owns it (i.e., scope->object == pobj). Therefore we
3194 * optimize JS_UNLOCK_OBJ(cx, pobj) into JS_UNLOCK_SCOPE(cx, scope).
3195 */
3196 scope = OBJ_SCOPE(pobj);
3198 attrs = sprop->attrs;
3199 if ((attrs & JSPROP_READONLY) ||
3200 (SCOPE_IS_SEALED(scope) && pobj == obj)) {
3201 JS_UNLOCK_SCOPE(cx, scope);
3202 if ((attrs & JSPROP_READONLY) && JS_VERSION_IS_ECMA(cx))
3203 return JS_TRUE;
3204 goto read_only_error;
3205 }
3207 if (pobj != obj) {
3208 /*
3209 * We found id in a prototype object: prepare to share or shadow.
3210 * NB: Thanks to the immutable, garbage-collected property tree
3211 * maintained by jsscope.c in cx->runtime, we needn't worry about
3212 * sprop going away behind our back after we've unlocked scope.
3213 */
3214 JS_UNLOCK_SCOPE(cx, scope);
3216 /* Don't clone a shared prototype property. */
3217 if (attrs & JSPROP_SHARED)
3218 return SPROP_SET(cx, sprop, obj, pobj, vp);
3220 /* Restore attrs to the ECMA default for new properties. */
3221 attrs = JSPROP_ENUMERATE;
3223 /*
3224 * Preserve the shortid, getter, and setter when shadowing any
3225 * property that has a shortid. An old API convention requires
3226 * that the property's getter and setter functions receive the
3227 * shortid, not id, when they are called on the shadow we are
3228 * about to create in obj's scope.
3229 */
3230 if (sprop->flags & SPROP_HAS_SHORTID) {
3231 flags = SPROP_HAS_SHORTID;
3232 shortid = sprop->shortid;
3233 getter = sprop->getter;
3234 setter = sprop->setter;
3235 }
3237 /*
3238 * Forget we found the proto-property now that we've copied any
3239 * needed member values.
3240 */
3241 sprop = NULL;
3242 }
3243 #ifdef __GNUC__ /* suppress bogus gcc warnings */
3244 } else {
3245 scope = NULL;
3246 #endif
3247 }
3249 if (!sprop) {
3250 if (SCOPE_IS_SEALED(OBJ_SCOPE(obj)) && OBJ_SCOPE(obj)->object == obj)
3251 goto read_only_error;
3253 /* Find or make a property descriptor with the right heritage. */
3254 JS_LOCK_OBJ(cx, obj);
3255 scope = js_GetMutableScope(cx, obj);
3256 if (!scope) {
3257 JS_UNLOCK_OBJ(cx, obj);
3258 return JS_FALSE;
3259 }
3260 if (clasp->flags & JSCLASS_SHARE_ALL_PROPERTIES)
3261 attrs |= JSPROP_SHARED;
3262 sprop = js_AddScopeProperty(cx, scope, id, getter, setter,
3263 SPROP_INVALID_SLOT, attrs, flags, shortid);
3264 if (!sprop) {
3265 JS_UNLOCK_SCOPE(cx, scope);
3266 return JS_FALSE;
3267 }
3269 /*
3270 * Initialize the new property value (passed to setter) to undefined.
3271 * Note that we store before calling addProperty, to match the order
3272 * in js_DefineNativeProperty.
3273 */
3274 if (SPROP_HAS_VALID_SLOT(sprop, scope))
3275 LOCKED_OBJ_SET_SLOT(obj, sprop->slot, JSVAL_VOID);
3277 /* XXXbe called with obj locked */
3278 ADD_PROPERTY_HELPER(cx, clasp, obj, scope, sprop, vp,
3279 js_RemoveScopeProperty(cx, scope, id);
3280 JS_UNLOCK_SCOPE(cx, scope);
3281 return JS_FALSE);
3283 PROPERTY_CACHE_FILL(&cx->runtime->propertyCache, obj, id, sprop);
3284 }
3286 /* Get the current property value from its slot. */
3287 slot = sprop->slot;
3288 if (slot != SPROP_INVALID_SLOT) {
3289 JS_ASSERT(slot < obj->map->freeslot);
3290 pval = LOCKED_OBJ_GET_SLOT(obj, slot);
3292 /* If sprop has a stub setter, keep scope locked and just store *vp. */
3293 if (!sprop->setter)
3294 goto set_slot;
3295 }
3297 /* Avoid deadlock by unlocking obj's scope while calling sprop's setter. */
3298 JS_UNLOCK_SCOPE(cx, scope);
3300 /* Let the setter modify vp before copying from it to obj->slots[slot]. */
3301 if (!SPROP_SET(cx, sprop, obj, obj, vp))
3302 return JS_FALSE;
3304 /* Relock obj's scope until we are done with sprop. */
3305 JS_LOCK_SCOPE(cx, scope);
3307 /*
3308 * Check whether sprop is still around (was not deleted), and whether it
3309 * has a slot (it may never have had one, or we may have lost a race with
3310 * someone who cleared scope).
3311 */
3312 if (SPROP_HAS_VALID_SLOT(sprop, scope)) {
3313 set_slot:
3314 GC_POKE(cx, pval);
3315 LOCKED_OBJ_SET_SLOT(obj, slot, *vp);
3316 }
3317 JS_UNLOCK_SCOPE(cx, scope);
3318 return JS_TRUE;
3320 read_only_error: {
3321 JSString *str = js_DecompileValueGenerator(cx,
3322 JSDVG_IGNORE_STACK,
3323 ID_TO_VALUE(id),
3324 NULL);
3325 if (str) {
3326 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3327 JSMSG_READ_ONLY,
3328 JS_GetStringBytes(str));
3329 }
3330 return JS_FALSE;
3331 }
3332 }
3334 JSBool
3335 js_GetAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
3336 uintN *attrsp)
3337 {
3338 JSBool noprop, ok;
3339 JSScopeProperty *sprop;
3341 noprop = !prop;
3342 if (noprop) {
3343 if (!js_LookupProperty(cx, obj, id, &obj, &prop))
3344 return JS_FALSE;
3345 if (!prop) {
3346 *attrsp = 0;
3347 return JS_TRUE;
3348 }
3349 if (!OBJ_IS_NATIVE(obj)) {
3350 ok = OBJ_GET_ATTRIBUTES(cx, obj, id, prop, attrsp);
3351 OBJ_DROP_PROPERTY(cx, obj, prop);
3352 return ok;
3353 }
3354 }
3355 sprop = (JSScopeProperty *)prop;
3356 *attrsp = sprop->attrs;
3357 if (noprop)
3358 OBJ_DROP_PROPERTY(cx, obj, prop);
3359 return JS_TRUE;
3360 }
3362 JSBool
3363 js_SetAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
3364 uintN *attrsp)
3365 {
3366 JSBool noprop, ok;
3367 JSScopeProperty *sprop;
3369 noprop = !prop;
3370 if (noprop) {
3371 if (!js_LookupProperty(cx, obj, id, &obj, &prop))
3372 return JS_FALSE;
3373 if (!prop)
3374 return JS_TRUE;
3375 if (!OBJ_IS_NATIVE(obj)) {
3376 ok = OBJ_SET_ATTRIBUTES(cx, obj, id, prop, attrsp);
3377 OBJ_DROP_PROPERTY(cx, obj, prop);
3378 return ok;
3379 }
3380 }
3381 sprop = (JSScopeProperty *)prop;
3382 sprop = js_ChangeNativePropertyAttrs(cx, obj, sprop, *attrsp, 0,
3383 sprop->getter, sprop->setter);
3384 if (noprop)
3385 OBJ_DROP_PROPERTY(cx, obj, prop);
3386 return (sprop != NULL);
3387 }
3389 JSBool
3390 js_DeleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *rval)
3391 {
3392 #if JS_HAS_PROP_DELETE
3394 JSObject *proto;
3395 JSProperty *prop;
3396 JSScopeProperty *sprop;
3397 JSString *str;
3398 JSScope *scope;
3399 JSBool ok;
3401 *rval = JS_VERSION_IS_ECMA(cx) ? JSVAL_TRUE : JSVAL_VOID;
3403 /*
3404 * Handle old bug that took empty string as zero index. Also convert
3405 * string indices to integers if appropriate.
3406 */
3407 CHECK_FOR_STRING_INDEX(id);
3409 if (!js_LookupProperty(cx, obj, id, &proto, &prop))
3410 return JS_FALSE;
3411 if (!prop || proto != obj) {
3412 /*
3413 * If the property was found in a native prototype, check whether it's
3414 * shared and permanent. Such a property stands for direct properties
3415 * in all delegating objects, matching ECMA semantics without bloating
3416 * each delegating object.
3417 */
3418 if (prop) {
3419 if (OBJ_IS_NATIVE(proto)) {
3420 sprop = (JSScopeProperty *)prop;
3421 if (SPROP_IS_SHARED_PERMANENT(sprop))
3422 *rval = JSVAL_FALSE;
3423 }
3424 OBJ_DROP_PROPERTY(cx, proto, prop);
3425 if (*rval == JSVAL_FALSE)
3426 return JS_TRUE;
3427 }
3429 /*
3430 * If no property, or the property comes unshared or impermanent from
3431 * a prototype, call the class's delProperty hook, passing rval as the
3432 * result parameter.
3433 */
3434 return OBJ_GET_CLASS(cx, obj)->delProperty(cx, obj, ID_TO_VALUE(id),
3435 rval);
3436 }
3438 sprop = (JSScopeProperty *)prop;
3439 if (sprop->attrs & JSPROP_PERMANENT) {
3440 OBJ_DROP_PROPERTY(cx, obj, prop);
3441 if (JS_VERSION_IS_ECMA(cx)) {
3442 *rval = JSVAL_FALSE;
3443 return JS_TRUE;
3444 }
3445 str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK,
3446 ID_TO_VALUE(id), NULL);
3447 if (str) {
3448 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3449 JSMSG_PERMANENT, JS_GetStringBytes(str));
3450 }
3451 return JS_FALSE;
3452 }
3454 /* XXXbe called with obj locked */
3455 if (!LOCKED_OBJ_GET_CLASS(obj)->delProperty(cx, obj, SPROP_USERID(sprop),
3456 rval)) {
3457 OBJ_DROP_PROPERTY(cx, obj, prop);
3458 return JS_FALSE;
3459 }
3461 scope = OBJ_SCOPE(obj);
3462 if (SPROP_HAS_VALID_SLOT(sprop, scope))
3463 GC_POKE(cx, LOCKED_OBJ_GET_SLOT(obj, sprop->slot));
3465 PROPERTY_CACHE_FILL(&cx->runtime->propertyCache, obj, id, NULL);
3466 ok = js_RemoveScopeProperty(cx, scope, id);
3467 OBJ_DROP_PROPERTY(cx, obj, prop);
3468 return ok;
3470 #else /* !JS_HAS_PROP_DELETE */
3472 jsval null = JSVAL_NULL;
3474 *rval = JSVAL_VOID;
3475 return js_SetProperty(cx, obj, id, &null);
3477 #endif /* !JS_HAS_PROP_DELETE */
3478 }
3480 JSBool
3481 js_DefaultValue(JSContext *cx, JSObject *obj, JSType hint, jsval *vp)
3482 {
3483 jsval v;
3484 JSString *str;
3486 v = OBJECT_TO_JSVAL(obj);
3487 switch (hint) {
3488 case JSTYPE_STRING:
3489 /*
3490 * Propagate the exception if js_TryMethod finds an appropriate
3491 * method, and calling that method returned failure.
3492 */
3493 if (!js_TryMethod(cx, obj, cx->runtime->atomState.toStringAtom, 0, NULL,
3494 &v)) {
3495 return JS_FALSE;
3496 }
3498 if (!JSVAL_IS_PRIMITIVE(v)) {
3499 if (!OBJ_GET_CLASS(cx, obj)->convert(cx, obj, hint, &v))
3500 return JS_FALSE;
3502 /*
3503 * JS1.2 never failed (except for malloc failure) to convert an
3504 * object to a string. ECMA requires an error if both toString
3505 * and valueOf fail to produce a primitive value.
3506 */
3507 if (!JSVAL_IS_PRIMITIVE(v) && JS_VERSION_IS_1_2(cx)) {
3508 char *bytes = JS_smprintf("[object %s]",
3509 OBJ_GET_CLASS(cx, obj)->name);
3510 if (!bytes)
3511 return JS_FALSE;
3512 str = JS_NewString(cx, bytes, strlen(bytes));
3513 if (!str) {
3514 free(bytes);
3515 return JS_FALSE;
3516 }
3517 v = STRING_TO_JSVAL(str);
3518 goto out;
3519 }
3520 }
3521 break;
3523 default:
3524 if (!OBJ_GET_CLASS(cx, obj)->convert(cx, obj, hint, &v))
3525 return JS_FALSE;
3526 if (!JSVAL_IS_PRIMITIVE(v)) {
3527 JSType type = JS_TypeOfValue(cx, v);
3528 if (type == hint ||
3529 (type == JSTYPE_FUNCTION && hint == JSTYPE_OBJECT)) {
3530 goto out;
3531 }
3532 /* Don't convert to string (source object literal) for JS1.2. */
3533 if (JS_VERSION_IS_1_2(cx) && hint == JSTYPE_BOOLEAN)
3534 goto out;
3535 if (!js_TryMethod(cx, obj, cx->runtime->atomState.toStringAtom, 0,
3536 NULL, &v))
3537 return JS_FALSE;
3538 }
3539 break;
3540 }
3541 if (!JSVAL_IS_PRIMITIVE(v)) {
3542 /* Avoid recursive death through js_DecompileValueGenerator. */
3543 if (hint == JSTYPE_STRING) {
3544 str = JS_InternString(cx, OBJ_GET_CLASS(cx, obj)->name);
3545 if (!str)
3546 return JS_FALSE;
3547 } else {
3548 str = NULL;
3549 }
3550 *vp = OBJECT_TO_JSVAL(obj);
3551 str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, str);
3552 if (str) {
3553 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
3554 JSMSG_CANT_CONVERT_TO,
3555 JS_GetStringBytes(str),
3556 (hint == JSTYPE_VOID)
3557 ? "primitive type"
3558 : js_type_str[hint]);
3559 }
3560 return JS_FALSE;
3561 }
3562 out:
3563 *vp = v;
3564 return JS_TRUE;
3565 }
3567 JSIdArray *
3568 js_NewIdArray(JSContext *cx, jsint length)
3569 {
3570 JSIdArray *ida;
3572 ida = (JSIdArray *)
3573 JS_malloc(cx, sizeof(JSIdArray) + (length-1) * sizeof(jsval));
3574 if (ida)
3575 ida->length = length;
3576 return ida;
3577 }
3579 JSIdArray *
3580 js_SetIdArrayLength(JSContext *cx, JSIdArray *ida, jsint length)
3581 {
3582 JSIdArray *rida;
3584 rida = (JSIdArray *)
3585 JS_realloc(cx, ida, sizeof(JSIdArray) + (length-1) * sizeof(jsval));
3586 if (!rida)
3587 JS_DestroyIdArray(cx, ida);
3588 else
3589 rida->length = length;
3590 return rida;
3591 }
3593 /* Private type used to iterate over all properties of a native JS object */
3594 struct JSNativeIteratorState {
3595 jsint next_index; /* index into jsid array */
3596 JSIdArray *ida; /* all property ids in enumeration */
3597 JSNativeIteratorState *next; /* double-linked list support */
3598 JSNativeIteratorState **prevp;
3599 };
3601 /*
3602 * This function is used to enumerate the properties of native JSObjects
3603 * and those host objects that do not define a JSNewEnumerateOp-style iterator
3604 * function.
3605 */
3606 JSBool
3607 js_Enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
3608 jsval *statep, jsid *idp)
3609 {
3610 JSRuntime *rt;
3611 JSObject *proto;
3612 JSClass *clasp;
3613 JSEnumerateOp enumerate;
3614 JSScopeProperty *sprop, *lastProp;
3615 jsint i, length;
3616 JSScope *scope;
3617 JSIdArray *ida;
3618 JSNativeIteratorState *state;
3620 rt = cx->runtime;
3621 clasp = OBJ_GET_CLASS(cx, obj);
3622 enumerate = clasp->enumerate;
3623 if (clasp->flags & JSCLASS_NEW_ENUMERATE)
3624 return ((JSNewEnumerateOp) enumerate)(cx, obj, enum_op, statep, idp);
3626 switch (enum_op) {
3627 case JSENUMERATE_INIT:
3628 if (!enumerate(cx, obj))
3629 return JS_FALSE;
3630 length = 0;
3632 /*
3633 * The set of all property ids is pre-computed when the iterator
3634 * is initialized so as to avoid problems with properties being
3635 * deleted during the iteration.
3636 */
3637 JS_LOCK_OBJ(cx, obj);
3638 scope = OBJ_SCOPE(obj);
3640 /*
3641 * If this object shares a scope with its prototype, don't enumerate
3642 * its properties. Otherwise they will be enumerated a second time
3643 * when the prototype object is enumerated.
3644 */
3645 proto = OBJ_GET_PROTO(cx, obj);
3646 if (proto && scope == OBJ_SCOPE(proto)) {
3647 ida = js_NewIdArray(cx, 0);
3648 if (!ida) {
3649 JS_UNLOCK_OBJ(cx, obj);
3650 return JS_FALSE;
3651 }
3652 } else {
3653 /* Object has a private scope; Enumerate all props in scope. */
3654 for (sprop = lastProp = SCOPE_LAST_PROP(scope); sprop;
3655 sprop = sprop->parent) {
3656 if ((
3657 #ifdef DUMP_CALL_TABLE
3658 (cx->options & JSOPTION_LOGCALL_TOSOURCE) ||
3659 #endif
3660 (sprop->attrs & JSPROP_ENUMERATE)) &&
3661 !(sprop->flags & SPROP_IS_ALIAS) &&
3662 (!SCOPE_HAD_MIDDLE_DELETE(scope) ||
3663 SCOPE_HAS_PROPERTY(scope, sprop))) {
3664 length++;
3665 }
3666 }
3667 ida = js_NewIdArray(cx, length);
3668 if (!ida) {
3669 JS_UNLOCK_OBJ(cx, obj);
3670 return JS_FALSE;
3671 }
3672 i = length;
3673 for (sprop = lastProp; sprop; sprop = sprop->parent) {
3674 if ((
3675 #ifdef DUMP_CALL_TABLE
3676 (cx->options & JSOPTION_LOGCALL_TOSOURCE) ||
3677 #endif
3678 (sprop->attrs & JSPROP_ENUMERATE)) &&
3679 !(sprop->flags & SPROP_IS_ALIAS) &&
3680 (!SCOPE_HAD_MIDDLE_DELETE(scope) ||
3681 SCOPE_HAS_PROPERTY(scope, sprop))) {
3682 JS_ASSERT(i > 0);
3683 ida->vector[--i] = sprop->id;
3684 }
3685 }
3686 }
3687 JS_UNLOCK_OBJ(cx, obj);
3689 state = (JSNativeIteratorState *)
3690 JS_malloc(cx, sizeof(JSNativeIteratorState));
3691 if (!state) {
3692 JS_DestroyIdArray(cx, ida);
3693 return JS_FALSE;
3694 }
3695 state->ida = ida;
3696 state->next_index = 0;
3698 JS_LOCK_RUNTIME(rt);
3699 state->next = rt->nativeIteratorStates;
3700 if (state->next)
3701 state->next->prevp = &state->next;
3702 state->prevp = &rt->nativeIteratorStates;
3703 *state->prevp = state;
3704 JS_UNLOCK_RUNTIME(rt);
3706 *statep = PRIVATE_TO_JSVAL(state);
3707 if (idp)
3708 *idp = INT_TO_JSVAL(length);
3709 break;
3711 case JSENUMERATE_NEXT:
3712 state = (JSNativeIteratorState *) JSVAL_TO_PRIVATE(*statep);
3713 ida = state->ida;
3714 length = ida->length;
3715 if (state->next_index != length) {
3716 *idp = ida->vector[state->next_index++];
3717 break;
3718 }
3719 /* FALL THROUGH */
3721 case JSENUMERATE_DESTROY:
3722 state = (JSNativeIteratorState *) JSVAL_TO_PRIVATE(*statep);
3724 JS_LOCK_RUNTIME(rt);
3725 JS_ASSERT(rt->nativeIteratorStates);
3726 JS_ASSERT(*state->prevp == state);
3727 if (state->next) {
3728 JS_ASSERT(state->next->prevp == &state->next);
3729 state->next->prevp = state->prevp;
3730 }
3731 *state->prevp = state->next;
3732 JS_UNLOCK_RUNTIME(rt);
3734 JS_DestroyIdArray(cx, state->ida);
3735 JS_free(cx, state);
3736 *statep = JSVAL_NULL;
3737 break;
3738 }
3739 return JS_TRUE;
3740 }
3742 void
3743 js_MarkNativeIteratorStates(JSContext *cx)
3744 {
3745 JSNativeIteratorState *state;
3746 jsid *cursor, *end, id;
3748 state = cx->runtime->nativeIteratorStates;
3749 if (!state)
3750 return;
3752 do {
3753 JS_ASSERT(*state->prevp == state);
3754 cursor = state->ida->vector;
3755 end = cursor + state->ida->length;
3756 for (; cursor != end; ++cursor) {
3757 id = *cursor;
3758 if (JSID_IS_ATOM(id)) {
3759 GC_MARK_ATOM(cx, JSID_TO_ATOM(id), NULL);
3760 } else if (JSID_IS_OBJECT(id)) {
3761 GC_MARK(cx, JSID_TO_OBJECT(id), "ida->vector[i]", NULL);
3762 }
3763 }
3764 } while ((state = state->next) != NULL);
3765 }
3768 JSBool
3769 js_CheckAccess(JSContext *cx, JSObject *obj, jsid id, JSAccessMode mode,
3770 jsval *vp, uintN *attrsp)
3771 {
3772 JSBool writing;
3773 JSObject *pobj;
3774 JSProperty *prop;
3775 JSClass *clasp;
3776 JSScopeProperty *sprop;
3777 JSCheckAccessOp check;
3779 writing = (mode & JSACC_WRITE) != 0;
3780 switch (mode & JSACC_TYPEMASK) {
3781 case JSACC_PROTO:
3782 pobj = obj;
3783 if (!writing)
3784 *vp = OBJ_GET_SLOT(cx, obj, JSSLOT_PROTO);
3785 *attrsp = JSPROP_PERMANENT;
3786 break;
3788 case JSACC_PARENT:
3789 JS_ASSERT(!writing);
3790 pobj = obj;
3791 *vp = OBJ_GET_SLOT(cx, obj, JSSLOT_PARENT);
3792 *attrsp = JSPROP_READONLY | JSPROP_PERMANENT;
3793 break;
3795 default:
3796 if (!js_LookupProperty(cx, obj, id, &pobj, &prop))
3797 return JS_FALSE;
3798 if (!prop) {
3799 if (!writing)
3800 *vp = JSVAL_VOID;
3801 *attrsp = 0;
3802 clasp = OBJ_GET_CLASS(cx, obj);
3803 return !clasp->checkAccess ||
3804 clasp->checkAccess(cx, obj, ID_TO_VALUE(id), mode, vp);
3805 }
3806 if (!OBJ_IS_NATIVE(pobj)) {
3807 OBJ_DROP_PROPERTY(cx, pobj, prop);
3808 return OBJ_CHECK_ACCESS(cx, pobj, id, mode, vp, attrsp);
3809 }
3811 sprop = (JSScopeProperty *)prop;
3812 *attrsp = sprop->attrs;
3813 if (!writing) {
3814 *vp = (SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(pobj)))
3815 ? LOCKED_OBJ_GET_SLOT(pobj, sprop->slot)
3816 : JSVAL_VOID;
3817 }
3818 OBJ_DROP_PROPERTY(cx, pobj, prop);
3819 }
3821 /*
3822 * If obj's class has a stub (null) checkAccess hook, use the per-runtime
3823 * checkObjectAccess callback, if configured.
3824 *
3825 * We don't want to require all classes to supply a checkAccess hook; we
3826 * need that hook only for certain classes used when precompiling scripts
3827 * and functions ("brutal sharing"). But for general safety of built-in
3828 * magic properties such as __proto__ and __parent__, we route all access
3829 * checks, even for classes that stub out checkAccess, through the global
3830 * checkObjectAccess hook. This covers precompilation-based sharing and
3831 * (possibly unintended) runtime sharing across trust boundaries.
3832 */
3833 clasp = OBJ_GET_CLASS(cx, pobj);
3834 check = clasp->checkAccess;
3835 if (!check)
3836 check = cx->runtime->checkObjectAccess;
3837 return !check || check(cx, pobj, ID_TO_VALUE(id), mode, vp);
3838 }
3840 #ifdef JS_THREADSAFE
3841 void
3842 js_DropProperty(JSContext *cx, JSObject *obj, JSProperty *prop)
3843 {
3844 JS_UNLOCK_OBJ(cx, obj);
3845 }
3846 #endif
3848 static void
3849 ReportIsNotFunction(JSContext *cx, jsval *vp, uintN flags)
3850 {
3851 /*
3852 * The decompiler may need to access the args of the function in
3853 * progress rather than the one we had hoped to call.
3854 * So we switch the cx->fp to the frame below us. We stick the
3855 * current frame in the dormantFrameChain to protect it from gc.
3856 */
3858 JSStackFrame *fp = cx->fp;
3859 if (fp->down) {
3860 JS_ASSERT(!fp->dormantNext);
3861 fp->dormantNext = cx->dormantFrameChain;
3862 cx->dormantFrameChain = fp;
3863 cx->fp = fp->down;
3864 }
3866 js_ReportIsNotFunction(cx, vp, flags);
3868 if (fp->down) {
3869 JS_ASSERT(cx->dormantFrameChain == fp);
3870 cx->dormantFrameChain = fp->dormantNext;
3871 fp->dormantNext = NULL;
3872 cx->fp = fp;
3873 }
3874 }
3876 #ifdef NARCISSUS
3877 static JSBool
3878 GetCurrentExecutionContext(JSContext *cx, JSObject *obj, jsval *rval)
3879 {
3880 JSObject *tmp;
3881 jsval xcval;
3883 while ((tmp = OBJ_GET_PARENT(cx, obj)) != NULL)
3884 obj = tmp;
3885 if (!OBJ_GET_PROPERTY(cx, obj,
3886 ATOM_TO_JSID(cx->runtime->atomState
3887 .ExecutionContextAtom),
3888 &xcval)) {
3889 return JS_FALSE;
3890 }
3891 if (JSVAL_IS_PRIMITIVE(xcval)) {
3892 JS_ReportError(cx, "invalid ExecutionContext in global object");
3893 return JS_FALSE;
3894 }
3895 if (!OBJ_GET_PROPERTY(cx, JSVAL_TO_OBJECT(xcval),
3896 ATOM_TO_JSID(cx->runtime->atomState.currentAtom),
3897 rval)) {
3898 return JS_FALSE;
3899 }
3900 return JS_TRUE;
3901 }
3902 #endif
3904 JSBool
3905 js_Call(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
3906 {
3907 JSClass *clasp;
3909 clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(argv[-2]));
3910 if (!clasp->call) {
3911 #ifdef NARCISSUS
3912 JSObject *callee, *args;
3913 jsval fval, nargv[3];
3914 JSBool ok;
3916 callee = JSVAL_TO_OBJECT(argv[-2]);
3917 if (!OBJ_GET_PROPERTY(cx, callee,
3918 ATOM_TO_JSID(cx->runtime->atomState.callAtom),
3919 &fval)) {
3920 return JS_FALSE;
3921 }
3922 if (JSVAL_IS_FUNCTION(cx, fval)) {
3923 if (!GetCurrentExecutionContext(cx, obj, &nargv[2]))
3924 return JS_FALSE;
3925 args = js_GetArgsObject(cx, cx->fp);
3926 if (!args)
3927 return JS_FALSE;
3928 nargv[0] = OBJECT_TO_JSVAL(obj);
3929 nargv[1] = OBJECT_TO_JSVAL(args);
3930 return js_InternalCall(cx, callee, fval, 3, nargv, rval);
3931 }
3932 if (JSVAL_IS_OBJECT(fval) && JSVAL_TO_OBJECT(fval) != callee) {
3933 argv[-2] = fval;
3934 ok = js_Call(cx, obj, argc, argv, rval);
3935 argv[-2] = OBJECT_TO_JSVAL(callee);
3936 return ok;
3937 }
3938 #endif
3939 ReportIsNotFunction(cx, &argv[-2], 0);
3940 return JS_FALSE;
3941 }
3942 return clasp->call(cx, obj, argc, argv, rval);
3943 }
3945 JSBool
3946 js_Construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
3947 jsval *rval)
3948 {
3949 JSClass *clasp;
3951 clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(argv[-2]));
3952 if (!clasp->construct) {
3953 #ifdef NARCISSUS
3954 JSObject *callee, *args;
3955 jsval cval, nargv[2];
3956 JSBool ok;
3958 callee = JSVAL_TO_OBJECT(argv[-2]);
3959 if (!OBJ_GET_PROPERTY(cx, callee,
3960 ATOM_TO_JSID(cx->runtime->atomState
3961 .constructAtom),
3962 &cval)) {
3963 return JS_FALSE;
3964 }
3965 if (JSVAL_IS_FUNCTION(cx, cval)) {
3966 if (!GetCurrentExecutionContext(cx, obj, &nargv[1]))
3967 return JS_FALSE;
3968 args = js_GetArgsObject(cx, cx->fp);
3969 if (!args)
3970 return JS_FALSE;
3971 nargv[0] = OBJECT_TO_JSVAL(args);
3972 return js_InternalCall(cx, callee, cval, 2, nargv, rval);
3973 }
3974 if (JSVAL_IS_OBJECT(cval) && JSVAL_TO_OBJECT(cval) != callee) {
3975 argv[-2] = cval;
3976 ok = js_Call(cx, obj, argc, argv, rval);
3977 argv[-2] = OBJECT_TO_JSVAL(callee);
3978 return ok;
3979 }
3980 #endif
3981 ReportIsNotFunction(cx, &argv[-2], JSV2F_CONSTRUCT);
3982 return JS_FALSE;
3983 }
3984 return clasp->construct(cx, obj, argc, argv, rval);
3985 }
3987 JSBool
3988 js_HasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
3989 {
3990 JSClass *clasp;
3991 JSString *str;
3993 clasp = OBJ_GET_CLASS(cx, obj);
3994 if (clasp->hasInstance)
3995 return clasp->hasInstance(cx, obj, v, bp);
3996 #ifdef NARCISSUS
3997 {
3998 jsval fval, rval;
4000 if (!OBJ_GET_PROPERTY(cx, obj,
4001 ATOM_TO_JSID(cx->runtime->atomState
4002 .hasInstanceAtom),
4003 &fval)) {
4004 return JS_FALSE;
4005 }
4006 if (JSVAL_IS_FUNCTION(cx, fval)) {
4007 return js_InternalCall(cx, obj, fval, 1, &v, &rval) &&
4008 js_ValueToBoolean(cx, rval, bp);
4009 }
4010 }
4011 #endif
4012 str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK,
4013 OBJECT_TO_JSVAL(obj), NULL);
4014 if (str) {
4015 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
4016 JSMSG_BAD_INSTANCEOF_RHS,
4017 JS_GetStringBytes(str));
4018 }
4019 return JS_FALSE;
4020 }
4022 JSBool
4023 js_IsDelegate(JSContext *cx, JSObject *obj, jsval v, JSBool *bp)
4024 {
4025 JSObject *obj2;
4027 *bp = JS_FALSE;
4028 if (JSVAL_IS_PRIMITIVE(v))
4029 return JS_TRUE;
4030 obj2 = JSVAL_TO_OBJECT(v);
4031 while ((obj2 = OBJ_GET_PROTO(cx, obj2)) != NULL) {
4032 if (obj2 == obj) {
4033 *bp = JS_TRUE;
4034 break;
4035 }
4036 }
4037 return JS_TRUE;
4038 }
4040 JSBool
4041 js_GetClassPrototype(JSContext *cx, const char *name, JSObject **protop)
4042 {
4043 return GetClassPrototype(cx, NULL, name, protop);
4044 }
4046 static JSBool
4047 GetClassPrototype(JSContext *cx, JSObject *scope, const char *name,
4048 JSObject **protop)
4049 {
4050 jsval v;
4051 JSObject *ctor;
4053 if (!js_FindConstructor(cx, scope, name, &v))
4054 return JS_FALSE;
4055 if (JSVAL_IS_FUNCTION(cx, v)) {
4056 ctor = JSVAL_TO_OBJECT(v);
4057 if (!OBJ_GET_PROPERTY(cx, ctor,
4058 ATOM_TO_JSID(cx->runtime->atomState
4059 .classPrototypeAtom),
4060 &v)) {
4061 return JS_FALSE;
4062 }
4063 if (!JSVAL_IS_PRIMITIVE(v)) {
4064 /*
4065 * Set the newborn root in case v is otherwise unreferenced.
4066 * It's ok to overwrite newborn roots here, since the getter
4067 * called just above could have. Unlike the common GC rooting
4068 * model, our callers do not have to protect protop thanks to
4069 * this newborn root, since they all immediately create a new
4070 * instance that delegates to this object, or just query the
4071 * prototype for its class.
4072 */
4073 cx->newborn[GCX_OBJECT] = JSVAL_TO_GCTHING(v);
4074 }
4075 }
4076 *protop = JSVAL_IS_OBJECT(v) ? JSVAL_TO_OBJECT(v) : NULL;
4077 return JS_TRUE;
4078 }
4080 /*
4081 * For shared precompilation of function objects, we support cloning on entry
4082 * to an execution context in which the function declaration or expression
4083 * should be processed as if it were not precompiled, where the precompiled
4084 * function's scope chain does not match the execution context's. The cloned
4085 * function object carries its execution-context scope in its parent slot; it
4086 * links to the precompiled function (the "clone-parent") via its proto slot.
4087 *
4088 * Note that this prototype-based delegation leaves an unchecked access path
4089 * from the clone to the clone-parent's 'constructor' property. If the clone
4090 * lives in a less privileged or shared scope than the clone-parent, this is
4091 * a security hole, a sharing hazard, or both. Therefore we check all such
4092 * accesses with the following getter/setter pair, which we use when defining
4093 * 'constructor' in f.prototype for all function objects f.
4094 */
4095 static JSBool
4096 CheckCtorGetAccess(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
4097 {
4098 JSAtom *atom;
4099 uintN attrs;
4101 atom = cx->runtime->atomState.constructorAtom;
4102 JS_ASSERT(id == ATOM_KEY(atom));
4103 return OBJ_CHECK_ACCESS(cx, obj, ATOM_TO_JSID(atom), JSACC_READ,
4104 vp, &attrs);
4105 }
4107 static JSBool
4108 CheckCtorSetAccess(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
4109 {
4110 JSAtom *atom;
4111 uintN attrs;
4113 atom = cx->runtime->atomState.constructorAtom;
4114 JS_ASSERT(id == ATOM_KEY(atom));
4115 return OBJ_CHECK_ACCESS(cx, obj, ATOM_TO_JSID(atom), JSACC_WRITE,
4116 vp, &attrs);
4117 }
4119 JSBool
4120 js_SetClassPrototype(JSContext *cx, JSObject *ctor, JSObject *proto,
4121 uintN attrs)
4122 {
4123 /*
4124 * Use the given attributes for the prototype property of the constructor,
4125 * as user-defined constructors have a DontDelete prototype (which may be
4126 * reset), while native or "system" constructors have DontEnum | ReadOnly |
4127 * DontDelete.
4128 */
4129 if (!OBJ_DEFINE_PROPERTY(cx, ctor,
4130 ATOM_TO_JSID(cx->runtime->atomState
4131 .classPrototypeAtom),
4132 OBJECT_TO_JSVAL(proto),
4133 JS_PropertyStub, JS_PropertyStub,
4134 attrs, NULL)) {
4135 return JS_FALSE;
4136 }
4138 /*
4139 * ECMA says that Object.prototype.constructor, or f.prototype.constructor
4140 * for a user-defined function f, is DontEnum.
4141 */
4142 return OBJ_DEFINE_PROPERTY(cx, proto,
4143 ATOM_TO_JSID(cx->runtime->atomState
4144 .constructorAtom),
4145 OBJECT_TO_JSVAL(ctor),
4146 CheckCtorGetAccess, CheckCtorSetAccess,
4147 0, NULL);
4148 }
4150 JSBool
4151 js_ValueToObject(JSContext *cx, jsval v, JSObject **objp)
4152 {
4153 JSObject *obj;
4155 if (JSVAL_IS_NULL(v) || JSVAL_IS_VOID(v)) {
4156 obj = NULL;
4157 } else if (JSVAL_IS_OBJECT(v)) {
4158 obj = JSVAL_TO_OBJECT(v);
4159 if (!OBJ_DEFAULT_VALUE(cx, obj, JSTYPE_OBJECT, &v))
4160 return JS_FALSE;
4161 if (JSVAL_IS_OBJECT(v))
4162 obj = JSVAL_TO_OBJECT(v);
4163 } else {
4164 if (JSVAL_IS_STRING(v)) {
4165 obj = js_StringToObject(cx, JSVAL_TO_STRING(v));
4166 } else if (JSVAL_IS_INT(v)) {
4167 obj = js_NumberToObject(cx, (jsdouble)JSVAL_TO_INT(v));
4168 } else if (JSVAL_IS_DOUBLE(v)) {
4169 obj = js_NumberToObject(cx, *JSVAL_TO_DOUBLE(v));
4170 } else {
4171 JS_ASSERT(JSVAL_IS_BOOLEAN(v));
4172 obj = js_BooleanToObject(cx, JSVAL_TO_BOOLEAN(v));
4173 }
4174 if (!obj)
4175 return JS_FALSE;
4176 }
4177 *objp = obj;
4178 return JS_TRUE;
4179 }
4181 JSObject *
4182 js_ValueToNonNullObject(JSContext *cx, jsval v)
4183 {
4184 JSObject *obj;
4185 JSString *str;
4187 if (!js_ValueToObject(cx, v, &obj))
4188 return NULL;
4189 if (!obj) {
4190 str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NULL);
4191 if (str) {
4192 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
4193 JSMSG_NO_PROPERTIES, JS_GetStringBytes(str));
4194 }
4195 }
4196 return obj;
4197 }
4199 JSBool
4200 js_TryValueOf(JSContext *cx, JSObject *obj, JSType type, jsval *rval)
4201 {
4202 #if JS_HAS_VALUEOF_HINT
4203 jsval argv[1];
4205 argv[0] = ATOM_KEY(cx->runtime->atomState.typeAtoms[type]);
4206 return js_TryMethod(cx, obj, cx->runtime->atomState.valueOfAtom, 1, argv,
4207 rval);
4208 #else
4209 return js_TryMethod(cx, obj, cx->runtime->atomState.valueOfAtom, 0, NULL,
4210 rval);
4211 #endif
4212 }
4214 JSBool
4215 js_TryMethod(JSContext *cx, JSObject *obj, JSAtom *atom,
4216 uintN argc, jsval *argv, jsval *rval)
4217 {
4218 JSErrorReporter older;
4219 jsid id;
4220 jsval fval;
4221 JSBool ok;
4222 int stackDummy;
4224 if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) {
4225 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED);
4226 return JS_FALSE;
4227 }
4229 /*
4230 * Report failure only if an appropriate method was found, and calling it
4231 * returned failure. We propagate failure in this case to make exceptions
4232 * behave properly.
4233 */
4234 older = JS_SetErrorReporter(cx, NULL);
4235 id = ATOM_TO_JSID(atom);
4236 fval = JSVAL_VOID;
4237 #if JS_HAS_XML_SUPPORT
4238 if (OBJECT_IS_XML(cx, obj)) {
4239 JSXMLObjectOps *ops;
4241 ops = (JSXMLObjectOps *) obj->map->ops;
4242 obj = ops->getMethod(cx, obj, id, &fval);
4243 ok = (obj != NULL);
4244 } else
4245 #endif
4246 {
4247 ok = OBJ_GET_PROPERTY(cx, obj, id, &fval);
4248 }
4249 if (!ok)
4250 JS_ClearPendingException(cx);
4251 ok = JSVAL_IS_PRIMITIVE(fval) ||
4252 js_InternalCall(cx, obj, fval, argc, argv, rval);
4253 JS_SetErrorReporter(cx, older);
4254 return ok;
4255 }
4257 #if JS_HAS_XDR
4259 #include "jsxdrapi.h"
4261 JSBool
4262 js_XDRObject(JSXDRState *xdr, JSObject **objp)
4263 {
4264 JSContext *cx;
4265 JSClass *clasp;
4266 const char *className;
4267 uint32 classId, classDef;
4268 JSBool ok;
4269 JSObject *proto;
4271 cx = xdr->cx;
4272 if (xdr->mode == JSXDR_ENCODE) {
4273 clasp = OBJ_GET_CLASS(cx, *objp);
4274 className = clasp->name;
4275 classId = JS_XDRFindClassIdByName(xdr, className);
4276 classDef = !classId;
4277 if (classDef && !JS_XDRRegisterClass(xdr, clasp, &classId))
4278 return JS_FALSE;
4279 } else {
4280 classDef = 0;
4281 className = NULL;
4282 clasp = NULL; /* quell GCC overwarning */
4283 }
4285 /* XDR a flag word followed (if true) by the class name. */
4286 if (!JS_XDRUint32(xdr, &classDef))
4287 return JS_FALSE;
4288 if (classDef && !JS_XDRCString(xdr, (char **) &className))
4289 return JS_FALSE;
4291 /* From here on, return through out: to free className if it was set. */
4292 ok = JS_XDRUint32(xdr, &classId);
4293 if (!ok)
4294 goto out;
4296 if (xdr->mode != JSXDR_ENCODE) {
4297 if (classDef) {
4298 ok = GetClassPrototype(cx, NULL, className, &proto);
4299 if (!ok)
4300 goto out;
4301 clasp = OBJ_GET_CLASS(cx, proto);
4302 ok = JS_XDRRegisterClass(xdr, clasp, &classId);
4303 if (!ok)
4304 goto out;
4305 } else {
4306 clasp = JS_XDRFindClassById(xdr, classId);
4307 if (!clasp) {
4308 char numBuf[12];
4309 JS_snprintf(numBuf, sizeof numBuf, "%ld", (long)classId);
4310 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
4311 JSMSG_CANT_FIND_CLASS, numBuf);
4312 ok = JS_FALSE;
4313 goto out;
4314 }
4315 }
4316 }
4318 if (!clasp->xdrObject) {
4319 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
4320 JSMSG_CANT_XDR_CLASS, clasp->name);
4321 ok = JS_FALSE;
4322 } else {
4323 ok = clasp->xdrObject(xdr, objp);
4324 }
4325 out:
4326 if (xdr->mode != JSXDR_ENCODE && className)
4327 JS_free(cx, (void *)className);
4328 return ok;
4329 }
4331 #endif /* JS_HAS_XDR */
4333 #ifdef DEBUG_brendan
4335 #include <stdio.h>
4336 #include <math.h>
4338 uint32 js_entry_count_max;
4339 uint32 js_entry_count_sum;
4340 double js_entry_count_sqsum;
4341 uint32 js_entry_count_hist[11];
4343 static void
4344 MeterEntryCount(uintN count)
4345 {
4346 if (count) {
4347 js_entry_count_sum += count;
4348 js_entry_count_sqsum += (double)count * count;
4349 if (count > js_entry_count_max)
4350 js_entry_count_max = count;
4351 }
4352 js_entry_count_hist[JS_MIN(count, 10)]++;
4353 }
4355 void
4356 js_DumpScopeMeters(JSRuntime *rt)
4357 {
4358 static FILE *logfp;
4359 if (!logfp)
4360 logfp = fopen("/tmp/scope.stats", "a");
4362 {
4363 double mean = 0., var = 0., sigma = 0.;
4364 double nscopes = rt->liveScopes;
4365 double nentrys = js_entry_count_sum;
4366 if (nscopes > 0 && nentrys >= 0) {
4367 mean = nentrys / nscopes;
4368 var = nscopes * js_entry_count_sqsum - nentrys * nentrys;
4369 if (var < 0.0 || nscopes <= 1)
4370 var = 0.0;
4371 else
4372 var /= nscopes * (nscopes - 1);
4374 /* Windows says sqrt(0.0) is "-1.#J" (?!) so we must test. */
4375 sigma = (var != 0.) ? sqrt(var) : 0.;
4376 }
4378 fprintf(logfp,
4379 "scopes %g entries %g mean %g sigma %g max %u",
4380 nscopes, nentrys, mean, sigma, js_entry_count_max);
4381 }
4383 fprintf(logfp, " histogram %u %u %u %u %u %u %u %u %u %u %u\n",
4384 js_entry_count_hist[0], js_entry_count_hist[1],
4385 js_entry_count_hist[2], js_entry_count_hist[3],
4386 js_entry_count_hist[4], js_entry_count_hist[5],
4387 js_entry_count_hist[6], js_entry_count_hist[7],
4388 js_entry_count_hist[8], js_entry_count_hist[9],
4389 js_entry_count_hist[10]);
4390 js_entry_count_sum = js_entry_count_max = 0;
4391 js_entry_count_sqsum = 0;
4392 memset(js_entry_count_hist, 0, sizeof js_entry_count_hist);
4393 fflush(logfp);
4394 }
4396 #endif /* DEBUG_brendan */
4398 uint32
4399 js_Mark(JSContext *cx, JSObject *obj, void *arg)
4400 {
4401 JSScope *scope;
4402 JSScopeProperty *sprop;
4403 JSClass *clasp;
4405 JS_ASSERT(OBJ_IS_NATIVE(obj));
4406 scope = OBJ_SCOPE(obj);
4407 #ifdef DEBUG_brendan
4408 if (scope->object == obj)
4409 MeterEntryCount(scope->entryCount);
4410 #endif
4412 JS_ASSERT(!SCOPE_LAST_PROP(scope) ||
4413 SCOPE_HAS_PROPERTY(scope, SCOPE_LAST_PROP(scope)));
4415 for (sprop = SCOPE_LAST_PROP(scope); sprop; sprop = sprop->parent) {
4416 if (SCOPE_HAD_MIDDLE_DELETE(scope) && !SCOPE_HAS_PROPERTY(scope, sprop))
4417 continue;
4418 MARK_SCOPE_PROPERTY(sprop);
4419 if (JSID_IS_ATOM(sprop->id))
4420 GC_MARK_ATOM(cx, JSID_TO_ATOM(sprop->id), arg);
4421 else if (JSID_IS_OBJECT(sprop->id))
4422 GC_MARK(cx, JSID_TO_OBJECT(sprop->id), "id", arg);
4424 #if JS_HAS_GETTER_SETTER
4425 if (sprop->attrs & (JSPROP_GETTER | JSPROP_SETTER)) {
4426 #ifdef GC_MARK_DEBUG
4427 char buf[64];
4428 JSAtom *atom = JSID_TO_ATOM(sprop->id);
4429 const char *id = (atom && ATOM_IS_STRING(atom))
4430 ? JS_GetStringBytes(ATOM_TO_STRING(atom))
4431 : "unknown";
4432 #endif
4434 if (sprop->attrs & JSPROP_GETTER) {
4435 #ifdef GC_MARK_DEBUG
4436 JS_snprintf(buf, sizeof buf, "%s %s",
4437 id, js_getter_str);
4438 #endif
4439 GC_MARK(cx,
4440 JSVAL_TO_GCTHING((jsval) sprop->getter),
4441 buf,
4442 arg);
4443 }
4444 if (sprop->attrs & JSPROP_SETTER) {
4445 #ifdef GC_MARK_DEBUG
4446 JS_snprintf(buf, sizeof buf, "%s %s",
4447 id, js_setter_str);
4448 #endif
4449 GC_MARK(cx,
4450 JSVAL_TO_GCTHING((jsval) sprop->setter),
4451 buf,
4452 arg);
4453 }
4454 }
4455 #endif /* JS_HAS_GETTER_SETTER */
4456 }
4458 /* No one runs while the GC is running, so we can use LOCKED_... here. */
4459 clasp = LOCKED_OBJ_GET_CLASS(obj);
4460 if (clasp->mark)
4461 (void) clasp->mark(cx, obj, arg);
4463 if (scope->object != obj) {
4464 /*
4465 * An unmutated object that shares a prototype's scope. We can't tell
4466 * how many slots are allocated and in use at obj->slots by looking at
4467 * scope, so we get obj->slots' length from its -1'st element.
4468 */
4469 return (uint32) obj->slots[-1];
4470 }
4471 return JS_MIN(scope->map.freeslot, scope->map.nslots);
4472 }
4474 void
4475 js_Clear(JSContext *cx, JSObject *obj)
4476 {
4477 JSScope *scope;
4478 JSRuntime *rt;
4479 JSScopeProperty *sprop;
4480 uint32 i, n;
4482 /*
4483 * Clear our scope and the property cache of all obj's properties only if
4484 * obj owns the scope (i.e., not if obj is unmutated and therefore sharing
4485 * its prototype's scope). NB: we do not clear any reserved slots lying
4486 * below JSSLOT_FREE(clasp).
4487 */
4488 JS_LOCK_OBJ(cx, obj);
4489 scope = OBJ_SCOPE(obj);
4490 if (scope->object == obj) {
4491 /* Clear the property cache before we clear the scope. */
4492 rt = cx->runtime;
4493 for (sprop = SCOPE_LAST_PROP(scope); sprop; sprop = sprop->parent) {
4494 if (!SCOPE_HAD_MIDDLE_DELETE(scope) ||
4495 SCOPE_HAS_PROPERTY(scope, sprop)) {
4496 PROPERTY_CACHE_FILL(&rt->propertyCache, obj, sprop->id, NULL);
4497 }
4498 }
4500 /* Now that we're done using scope->lastProp/table, clear scope. */
4501 js_ClearScope(cx, scope);
4503 /* Clear slot values and reset freeslot so we're consistent. */
4504 i = scope->map.nslots;
4505 n = JSSLOT_FREE(LOCKED_OBJ_GET_CLASS(obj));
4506 while (--i >= n)
4507 obj->slots[i] = JSVAL_VOID;
4508 scope->map.freeslot = n;
4509 }
4510 JS_UNLOCK_OBJ(cx, obj);
4511 }
4513 jsval
4514 js_GetRequiredSlot(JSContext *cx, JSObject *obj, uint32 slot)
4515 {
4516 jsval v;
4518 JS_LOCK_OBJ(cx, obj);
4519 v = (slot < (uint32) obj->slots[-1]) ? obj->slots[slot] : JSVAL_VOID;
4520 JS_UNLOCK_OBJ(cx, obj);
4521 return v;
4522 }
4524 JSBool
4525 js_SetRequiredSlot(JSContext *cx, JSObject *obj, uint32 slot, jsval v)
4526 {
4527 JSScope *scope;
4528 uint32 nslots;
4529 JSClass *clasp;
4530 jsval *newslots;
4532 JS_LOCK_OBJ(cx, obj);
4533 scope = OBJ_SCOPE(obj);
4534 nslots = (uint32) obj->slots[-1];
4535 if (slot >= nslots) {
4536 /*
4537 * At this point, obj may or may not own scope. If some path calls
4538 * js_GetMutableScope but does not add a slot-owning property, then
4539 * scope->object == obj but nslots will be nominal. If obj shares a
4540 * prototype's scope, then we cannot update scope->map here, but we
4541 * must update obj->slots[-1] when we grow obj->slots.
4542 *
4543 * See js_Mark, before the last return, where we make a special case
4544 * for unmutated (scope->object != obj) objects.
4545 */
4546 JS_ASSERT(nslots == JS_INITIAL_NSLOTS);
4547 clasp = LOCKED_OBJ_GET_CLASS(obj);
4548 nslots = JSSLOT_FREE(clasp);
4549 if (clasp->reserveSlots)
4550 nslots += clasp->reserveSlots(cx, obj);
4551 JS_ASSERT(slot < nslots);
4553 newslots = AllocSlots(cx, obj->slots, nslots);
4554 if (!newslots) {
4555 JS_UNLOCK_SCOPE(cx, scope);
4556 return JS_FALSE;
4557 }
4558 if (scope->object == obj)
4559 scope->map.nslots = nslots;
4560 obj->slots = newslots;
4561 }
4563 /* Whether or not we grew nslots, we may need to advance freeslot. */
4564 if (scope->object == obj && slot >= scope->map.freeslot)
4565 scope->map.freeslot = slot + 1;
4567 obj->slots[slot] = v;
4568 JS_UNLOCK_SCOPE(cx, scope);
4569 return JS_TRUE;
4570 }
4572 #ifdef DEBUG
4574 /* Routines to print out values during debugging. */
4576 void printChar(jschar *cp) {
4577 fprintf(stderr, "jschar* (0x%p) \"", (void *)cp);
4578 while (*cp)
4579 fputc(*cp++, stderr);
4580 fputc('"', stderr);
4581 fputc('\n', stderr);
4582 }
4584 void printString(JSString *str) {
4585 size_t i, n;
4586 jschar *s;
4587 fprintf(stderr, "string (0x%p) \"", (void *)str);
4588 s = JSSTRING_CHARS(str);
4589 for (i=0, n=JSSTRING_LENGTH(str); i < n; i++)
4590 fputc(s[i], stderr);
4591 fputc('"', stderr);
4592 fputc('\n', stderr);
4593 }
4595 void printVal(JSContext *cx, jsval val);
4597 void printObj(JSContext *cx, JSObject *jsobj) {
4598 jsuint i;
4599 jsval val;
4600 JSClass *clasp;
4602 fprintf(stderr, "object 0x%p\n", (void *)jsobj);
4603 clasp = OBJ_GET_CLASS(cx, jsobj);
4604 fprintf(stderr, "class 0x%p %s\n", (void *)clasp, clasp->name);
4605 for (i=0; i < jsobj->map->nslots; i++) {
4606 fprintf(stderr, "slot %3d ", i);
4607 val = jsobj->slots[i];
4608 if (JSVAL_IS_OBJECT(val))
4609 fprintf(stderr, "object 0x%p\n", (void *)JSVAL_TO_OBJECT(val));
4610 else
4611 printVal(cx, val);
4612 }
4613 }
4615 void printVal(JSContext *cx, jsval val) {
4616 fprintf(stderr, "val %d (0x%p) = ", (int)val, (void *)val);
4617 if (JSVAL_IS_NULL(val)) {
4618 fprintf(stderr, "null\n");
4619 } else if (JSVAL_IS_VOID(val)) {
4620 fprintf(stderr, "undefined\n");
4621 } else if (JSVAL_IS_OBJECT(val)) {
4622 printObj(cx, JSVAL_TO_OBJECT(val));
4623 } else if (JSVAL_IS_INT(val)) {
4624 fprintf(stderr, "(int) %d\n", JSVAL_TO_INT(val));
4625 } else if (JSVAL_IS_STRING(val)) {
4626 printString(JSVAL_TO_STRING(val));
4627 } else if (JSVAL_IS_DOUBLE(val)) {
4628 fprintf(stderr, "(double) %g\n", *JSVAL_TO_DOUBLE(val));
4629 } else {
4630 JS_ASSERT(JSVAL_IS_BOOLEAN(val));
4631 fprintf(stderr, "(boolean) %s\n",
4632 JSVAL_TO_BOOLEAN(val) ? "true" : "false");
4633 }
4634 fflush(stderr);
4635 }
4637 void printId(JSContext *cx, jsid id) {
4638 fprintf(stderr, "id %d (0x%p) is ", (int)id, (void *)id);
4639 printVal(cx, ID_TO_VALUE(id));
4640 }
4642 void printAtom(JSAtom *atom) {
4643 printString(ATOM_TO_STRING(atom));
4644 }
4646 #endif