/[jscoverage]/trunk/js/jsobj.cpp
ViewVC logotype

Contents of /trunk/js/jsobj.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 399 - (show annotations)
Tue Dec 9 03:37:47 2008 UTC (11 years, 9 months ago) by siliconforks
File size: 174575 byte(s)
Use SpiderMonkey from Firefox 3.1b2.

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 ***** */
40
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 "jsbit.h"
50 #include "jsutil.h" /* Added by JSIFY */
51 #include "jshash.h" /* Added by JSIFY */
52 #include "jsdhash.h"
53 #include "jsprf.h"
54 #include "jsapi.h"
55 #include "jsarray.h"
56 #include "jsatom.h"
57 #include "jsbool.h"
58 #include "jsbuiltins.h"
59 #include "jscntxt.h"
60 #include "jsversion.h"
61 #include "jsemit.h"
62 #include "jsfun.h"
63 #include "jsgc.h"
64 #include "jsinterp.h"
65 #include "jslock.h"
66 #include "jsnum.h"
67 #include "jsobj.h"
68 #include "jsopcode.h"
69 #include "jsparse.h"
70 #include "jsscope.h"
71 #include "jsscript.h"
72 #include "jsstr.h"
73 #include "jsdbgapi.h" /* whether or not JS_HAS_OBJ_WATCHPOINT */
74 #include "jsstaticcheck.h"
75
76 #if JS_HAS_GENERATORS
77 #include "jsiter.h"
78 #endif
79
80 #if JS_HAS_XML_SUPPORT
81 #include "jsxml.h"
82 #endif
83
84 #if JS_HAS_XDR
85 #include "jsxdrapi.h"
86 #endif
87
88 #ifdef INCLUDE_MOZILLA_DTRACE
89 #include "jsdtracef.h"
90 #endif
91
92 #include "jsautooplen.h"
93
94 #ifdef JS_THREADSAFE
95 #define NATIVE_DROP_PROPERTY js_DropProperty
96
97 extern void
98 js_DropProperty(JSContext *cx, JSObject *obj, JSProperty *prop);
99 #else
100 #define NATIVE_DROP_PROPERTY NULL
101 #endif
102
103 JS_FRIEND_DATA(JSObjectOps) js_ObjectOps = {
104 js_NewObjectMap, js_DestroyObjectMap,
105 js_LookupProperty, js_DefineProperty,
106 js_GetProperty, js_SetProperty,
107 js_GetAttributes, js_SetAttributes,
108 js_DeleteProperty, js_DefaultValue,
109 js_Enumerate, js_CheckAccess,
110 NULL, NATIVE_DROP_PROPERTY,
111 js_Call, js_Construct,
112 NULL, js_HasInstance,
113 js_SetProtoOrParent, js_SetProtoOrParent,
114 js_TraceObject, js_Clear,
115 js_GetRequiredSlot, js_SetRequiredSlot
116 };
117
118 JSClass js_ObjectClass = {
119 js_Object_str,
120 JSCLASS_HAS_CACHED_PROTO(JSProto_Object),
121 JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
122 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
123 JSCLASS_NO_OPTIONAL_MEMBERS
124 };
125
126 #if JS_HAS_OBJ_PROTO_PROP
127
128 static JSBool
129 obj_getSlot(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
130
131 static JSBool
132 obj_setSlot(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
133
134 static JSBool
135 obj_getCount(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
136
137 static JSPropertySpec object_props[] = {
138 /* These two must come first; see object_props[slot].name usage below. */
139 {js_proto_str, JSSLOT_PROTO, JSPROP_PERMANENT|JSPROP_SHARED,
140 obj_getSlot, obj_setSlot},
141 {js_parent_str,JSSLOT_PARENT,JSPROP_READONLY|JSPROP_PERMANENT|JSPROP_SHARED,
142 obj_getSlot, obj_setSlot},
143 {js_count_str, 0, JSPROP_READONLY|JSPROP_PERMANENT|JSPROP_SHARED,
144 obj_getCount, NULL},
145 {0,0,0,0,0}
146 };
147
148 /* NB: JSSLOT_PROTO and JSSLOT_PARENT are already indexes into object_props. */
149 #define JSSLOT_COUNT 2
150
151 static JSBool
152 ReportStrictSlot(JSContext *cx, uint32 slot)
153 {
154 if (slot == JSSLOT_PROTO)
155 return JS_TRUE;
156 return JS_ReportErrorFlagsAndNumber(cx,
157 JSREPORT_WARNING | JSREPORT_STRICT,
158 js_GetErrorMessage, NULL,
159 JSMSG_DEPRECATED_USAGE,
160 object_props[slot].name);
161 }
162
163 static JSBool
164 obj_getSlot(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
165 {
166 uint32 slot;
167 jsid propid;
168 JSAccessMode mode;
169 uintN attrs;
170 JSObject *pobj;
171 JSClass *clasp;
172 JSExtendedClass *xclasp;
173
174 slot = (uint32) JSVAL_TO_INT(id);
175 if (id == INT_TO_JSVAL(JSSLOT_PROTO)) {
176 propid = ATOM_TO_JSID(cx->runtime->atomState.protoAtom);
177 mode = JSACC_PROTO;
178 } else {
179 propid = ATOM_TO_JSID(cx->runtime->atomState.parentAtom);
180 mode = JSACC_PARENT;
181 }
182
183 /* Let OBJ_CHECK_ACCESS get the slot's value, based on the access mode. */
184 if (!OBJ_CHECK_ACCESS(cx, obj, propid, mode, vp, &attrs))
185 return JS_FALSE;
186
187 pobj = JSVAL_TO_OBJECT(*vp);
188 if (pobj) {
189 clasp = OBJ_GET_CLASS(cx, pobj);
190 if (clasp == &js_CallClass || clasp == &js_BlockClass) {
191 /* Censor activations and lexical scopes per ECMA-262. */
192 *vp = JSVAL_NULL;
193 } else if (clasp->flags & JSCLASS_IS_EXTENDED) {
194 xclasp = (JSExtendedClass *) clasp;
195 if (xclasp->outerObject) {
196 pobj = xclasp->outerObject(cx, pobj);
197 if (!pobj)
198 return JS_FALSE;
199 *vp = OBJECT_TO_JSVAL(pobj);
200 }
201 }
202 }
203 return JS_TRUE;
204 }
205
206 static JSBool
207 obj_setSlot(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
208 {
209 JSObject *pobj;
210 uint32 slot;
211 jsid propid;
212 uintN attrs;
213
214 if (!JSVAL_IS_OBJECT(*vp))
215 return JS_TRUE;
216 pobj = JSVAL_TO_OBJECT(*vp);
217
218 if (pobj) {
219 /*
220 * Innerize pobj here to avoid sticking unwanted properties on the
221 * outer object. This ensures that any with statements only grant
222 * access to the inner object.
223 */
224 OBJ_TO_INNER_OBJECT(cx, pobj);
225 if (!pobj)
226 return JS_FALSE;
227 }
228 slot = (uint32) JSVAL_TO_INT(id);
229 if (JS_HAS_STRICT_OPTION(cx) && !ReportStrictSlot(cx, slot))
230 return JS_FALSE;
231
232 /* __parent__ is readonly and permanent, only __proto__ may be set. */
233 propid = ATOM_TO_JSID(cx->runtime->atomState.protoAtom);
234 if (!OBJ_CHECK_ACCESS(cx, obj, propid,
235 (JSAccessMode)(JSACC_PROTO|JSACC_WRITE), vp,
236 &attrs)) {
237 return JS_FALSE;
238 }
239
240 return js_SetProtoOrParent(cx, obj, slot, pobj);
241 }
242
243 static JSBool
244 obj_getCount(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
245 {
246 jsval iter_state;
247 jsid num_properties;
248 JSBool ok;
249
250 if (JS_HAS_STRICT_OPTION(cx) && !ReportStrictSlot(cx, JSSLOT_COUNT))
251 return JS_FALSE;
252
253 /* Get the number of properties to enumerate. */
254 iter_state = JSVAL_NULL;
255 ok = OBJ_ENUMERATE(cx, obj, JSENUMERATE_INIT, &iter_state, &num_properties);
256 if (!ok)
257 goto out;
258
259 if (!JSVAL_IS_INT(num_properties)) {
260 JS_ASSERT(0);
261 *vp = JSVAL_ZERO;
262 goto out;
263 }
264 *vp = num_properties;
265
266 out:
267 if (iter_state != JSVAL_NULL)
268 ok = OBJ_ENUMERATE(cx, obj, JSENUMERATE_DESTROY, &iter_state, 0);
269 return ok;
270 }
271
272 #else /* !JS_HAS_OBJ_PROTO_PROP */
273
274 #define object_props NULL
275
276 #endif /* !JS_HAS_OBJ_PROTO_PROP */
277
278 JSBool
279 js_SetProtoOrParent(JSContext *cx, JSObject *obj, uint32 slot, JSObject *pobj)
280 {
281 JSSetSlotRequest ssr;
282 JSRuntime *rt;
283
284 /* Optimize the null case to avoid the unnecessary overhead of js_GC. */
285 if (!pobj) {
286 JS_LOCK_OBJ(cx, obj);
287 if (slot == JSSLOT_PROTO && !js_GetMutableScope(cx, obj)) {
288 JS_UNLOCK_OBJ(cx, obj);
289 return JS_FALSE;
290 }
291 LOCKED_OBJ_SET_SLOT(obj, slot, JSVAL_NULL);
292 JS_UNLOCK_OBJ(cx, obj);
293 return JS_TRUE;
294 }
295
296 ssr.obj = obj;
297 ssr.pobj = pobj;
298 ssr.slot = (uint16) slot;
299 ssr.errnum = (uint16) JSMSG_NOT_AN_ERROR;
300
301 rt = cx->runtime;
302 JS_LOCK_GC(rt);
303 ssr.next = rt->setSlotRequests;
304 rt->setSlotRequests = &ssr;
305 for (;;) {
306 js_GC(cx, GC_SET_SLOT_REQUEST);
307 JS_UNLOCK_GC(rt);
308 if (!rt->setSlotRequests)
309 break;
310 JS_LOCK_GC(rt);
311 }
312
313 if (ssr.errnum != JSMSG_NOT_AN_ERROR) {
314 if (ssr.errnum == JSMSG_OUT_OF_MEMORY) {
315 JS_ReportOutOfMemory(cx);
316 } else {
317 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, ssr.errnum,
318 #if JS_HAS_OBJ_PROTO_PROP
319 object_props[slot].name
320 #else
321 (slot == JSSLOT_PROTO) ? js_proto_str
322 : js_parent_str
323 #endif
324 );
325 }
326 return JS_FALSE;
327 }
328
329 // Maintain the "any Array prototype has indexed properties hazard" flag.
330 if (slot == JSSLOT_PROTO &&
331 OBJ_IS_ARRAY(cx, pobj) &&
332 pobj->fslots[JSSLOT_ARRAY_LENGTH] != 0) {
333 rt->anyArrayProtoHasElement = JS_TRUE;
334 }
335 return JS_TRUE;
336 }
337
338 static JSHashNumber
339 js_hash_object(const void *key)
340 {
341 return (JSHashNumber)JS_PTR_TO_UINT32(key) >> JSVAL_TAGBITS;
342 }
343
344 static JSHashEntry *
345 MarkSharpObjects(JSContext *cx, JSObject *obj, JSIdArray **idap)
346 {
347 JSSharpObjectMap *map;
348 JSHashTable *table;
349 JSHashNumber hash;
350 JSHashEntry **hep, *he;
351 jsatomid sharpid;
352 JSIdArray *ida;
353 JSBool ok;
354 jsint i, length;
355 jsid id;
356 #if JS_HAS_GETTER_SETTER
357 JSObject *obj2;
358 JSProperty *prop;
359 uintN attrs;
360 #endif
361 jsval val;
362
363 JS_CHECK_RECURSION(cx, return NULL);
364
365 map = &cx->sharpObjectMap;
366 table = map->table;
367 hash = js_hash_object(obj);
368 hep = JS_HashTableRawLookup(table, hash, obj);
369 he = *hep;
370 if (!he) {
371 sharpid = 0;
372 he = JS_HashTableRawAdd(table, hep, hash, obj,
373 JS_UINT32_TO_PTR(sharpid));
374 if (!he) {
375 JS_ReportOutOfMemory(cx);
376 return NULL;
377 }
378
379 /*
380 * Increment map->depth to protect js_EnterSharpObject from reentering
381 * itself badly. Without this fix, if we reenter the basis case where
382 * map->depth == 0, when unwinding the inner call we will destroy the
383 * newly-created hash table and crash.
384 */
385 ++map->depth;
386 ida = JS_Enumerate(cx, obj);
387 --map->depth;
388 if (!ida)
389 return NULL;
390
391 ok = JS_TRUE;
392 for (i = 0, length = ida->length; i < length; i++) {
393 id = ida->vector[i];
394 #if JS_HAS_GETTER_SETTER
395 ok = OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop);
396 if (!ok)
397 break;
398 if (!prop)
399 continue;
400 ok = OBJ_GET_ATTRIBUTES(cx, obj2, id, prop, &attrs);
401 if (ok) {
402 if (OBJ_IS_NATIVE(obj2) &&
403 (attrs & (JSPROP_GETTER | JSPROP_SETTER))) {
404 val = JSVAL_NULL;
405 if (attrs & JSPROP_GETTER)
406 val = (jsval) ((JSScopeProperty*)prop)->getter;
407 if (attrs & JSPROP_SETTER) {
408 if (val != JSVAL_NULL) {
409 /* Mark the getter, then set val to setter. */
410 ok = (MarkSharpObjects(cx, JSVAL_TO_OBJECT(val),
411 NULL)
412 != NULL);
413 }
414 val = (jsval) ((JSScopeProperty*)prop)->setter;
415 }
416 } else {
417 ok = OBJ_GET_PROPERTY(cx, obj, id, &val);
418 }
419 }
420 OBJ_DROP_PROPERTY(cx, obj2, prop);
421 #else
422 ok = OBJ_GET_PROPERTY(cx, obj, id, &val);
423 #endif
424 if (!ok)
425 break;
426 if (!JSVAL_IS_PRIMITIVE(val) &&
427 !MarkSharpObjects(cx, JSVAL_TO_OBJECT(val), NULL)) {
428 ok = JS_FALSE;
429 break;
430 }
431 }
432 if (!ok || !idap)
433 JS_DestroyIdArray(cx, ida);
434 if (!ok)
435 return NULL;
436 } else {
437 sharpid = JS_PTR_TO_UINT32(he->value);
438 if (sharpid == 0) {
439 sharpid = ++map->sharpgen << SHARP_ID_SHIFT;
440 he->value = JS_UINT32_TO_PTR(sharpid);
441 }
442 ida = NULL;
443 }
444 if (idap)
445 *idap = ida;
446 return he;
447 }
448
449 JSHashEntry *
450 js_EnterSharpObject(JSContext *cx, JSObject *obj, JSIdArray **idap,
451 jschar **sp)
452 {
453 JSSharpObjectMap *map;
454 JSHashTable *table;
455 JSIdArray *ida;
456 JSHashNumber hash;
457 JSHashEntry *he, **hep;
458 jsatomid sharpid;
459 char buf[20];
460 size_t len;
461
462 if (!JS_CHECK_OPERATION_LIMIT(cx, JSOW_ENTER_SHARP))
463 return NULL;
464
465 /* Set to null in case we return an early error. */
466 *sp = NULL;
467 map = &cx->sharpObjectMap;
468 table = map->table;
469 if (!table) {
470 table = JS_NewHashTable(8, js_hash_object, JS_CompareValues,
471 JS_CompareValues, NULL, NULL);
472 if (!table) {
473 JS_ReportOutOfMemory(cx);
474 return NULL;
475 }
476 map->table = table;
477 JS_KEEP_ATOMS(cx->runtime);
478 }
479
480 /* From this point the control must flow either through out: or bad:. */
481 ida = NULL;
482 if (map->depth == 0) {
483 he = MarkSharpObjects(cx, obj, &ida);
484 if (!he)
485 goto bad;
486 JS_ASSERT((JS_PTR_TO_UINT32(he->value) & SHARP_BIT) == 0);
487 if (!idap) {
488 JS_DestroyIdArray(cx, ida);
489 ida = NULL;
490 }
491 } else {
492 hash = js_hash_object(obj);
493 hep = JS_HashTableRawLookup(table, hash, obj);
494 he = *hep;
495
496 /*
497 * It's possible that the value of a property has changed from the
498 * first time the object's properties are traversed (when the property
499 * ids are entered into the hash table) to the second (when they are
500 * converted to strings), i.e., the OBJ_GET_PROPERTY() call is not
501 * idempotent.
502 */
503 if (!he) {
504 he = JS_HashTableRawAdd(table, hep, hash, obj, NULL);
505 if (!he) {
506 JS_ReportOutOfMemory(cx);
507 goto bad;
508 }
509 sharpid = 0;
510 goto out;
511 }
512 }
513
514 sharpid = JS_PTR_TO_UINT32(he->value);
515 if (sharpid != 0) {
516 len = JS_snprintf(buf, sizeof buf, "#%u%c",
517 sharpid >> SHARP_ID_SHIFT,
518 (sharpid & SHARP_BIT) ? '#' : '=');
519 *sp = js_InflateString(cx, buf, &len);
520 if (!*sp) {
521 if (ida)
522 JS_DestroyIdArray(cx, ida);
523 goto bad;
524 }
525 }
526
527 out:
528 JS_ASSERT(he);
529 if ((sharpid & SHARP_BIT) == 0) {
530 if (idap && !ida) {
531 ida = JS_Enumerate(cx, obj);
532 if (!ida) {
533 if (*sp) {
534 JS_free(cx, *sp);
535 *sp = NULL;
536 }
537 goto bad;
538 }
539 }
540 map->depth++;
541 }
542
543 if (idap)
544 *idap = ida;
545 return he;
546
547 bad:
548 /* Clean up the sharpObjectMap table on outermost error. */
549 if (map->depth == 0) {
550 JS_UNKEEP_ATOMS(cx->runtime);
551 map->sharpgen = 0;
552 JS_HashTableDestroy(map->table);
553 map->table = NULL;
554 }
555 return NULL;
556 }
557
558 void
559 js_LeaveSharpObject(JSContext *cx, JSIdArray **idap)
560 {
561 JSSharpObjectMap *map;
562 JSIdArray *ida;
563
564 map = &cx->sharpObjectMap;
565 JS_ASSERT(map->depth > 0);
566 if (--map->depth == 0) {
567 JS_UNKEEP_ATOMS(cx->runtime);
568 map->sharpgen = 0;
569 JS_HashTableDestroy(map->table);
570 map->table = NULL;
571 }
572 if (idap) {
573 ida = *idap;
574 if (ida) {
575 JS_DestroyIdArray(cx, ida);
576 *idap = NULL;
577 }
578 }
579 }
580
581 static intN
582 gc_sharp_table_entry_marker(JSHashEntry *he, intN i, void *arg)
583 {
584 JS_CALL_OBJECT_TRACER((JSTracer *)arg, (JSObject *)he->key,
585 "sharp table entry");
586 return JS_DHASH_NEXT;
587 }
588
589 void
590 js_TraceSharpMap(JSTracer *trc, JSSharpObjectMap *map)
591 {
592 JS_ASSERT(map->depth > 0);
593 JS_ASSERT(map->table);
594
595 /*
596 * During recursive calls to MarkSharpObjects a non-native object or
597 * object with a custom getProperty method can potentially return an
598 * unrooted value or even cut from the object graph an argument of one of
599 * MarkSharpObjects recursive invocations. So we must protect map->table
600 * entries against GC.
601 *
602 * We can not simply use JSTempValueRooter to mark the obj argument of
603 * MarkSharpObjects during recursion as we have to protect *all* entries
604 * in JSSharpObjectMap including those that contains otherwise unreachable
605 * objects just allocated through custom getProperty. Otherwise newer
606 * allocations can re-use the address of an object stored in the hashtable
607 * confusing js_EnterSharpObject. So to address the problem we simply
608 * mark all objects from map->table.
609 *
610 * An alternative "proper" solution is to use JSTempValueRooter in
611 * MarkSharpObjects with code to remove during finalization entries
612 * with otherwise unreachable objects. But this is way too complex
613 * to justify spending efforts.
614 */
615 JS_HashTableEnumerateEntries(map->table, gc_sharp_table_entry_marker, trc);
616 }
617
618 #if JS_HAS_TOSOURCE
619 static JSBool
620 obj_toSource(JSContext *cx, uintN argc, jsval *vp)
621 {
622 JSBool ok, outermost;
623 JSObject *obj;
624 JSHashEntry *he;
625 JSIdArray *ida;
626 jschar *chars, *ochars, *vsharp;
627 const jschar *idstrchars, *vchars;
628 size_t nchars, idstrlength, gsoplength, vlength, vsharplength, curlen;
629 const char *comma;
630 jsint i, j, length, valcnt;
631 jsid id;
632 #if JS_HAS_GETTER_SETTER
633 JSObject *obj2;
634 JSProperty *prop;
635 uintN attrs;
636 #endif
637 jsval *val;
638 jsval localroot[4] = {JSVAL_NULL, JSVAL_NULL, JSVAL_NULL, JSVAL_NULL};
639 JSTempValueRooter tvr;
640 JSString *gsopold[2];
641 JSString *gsop[2];
642 JSString *idstr, *valstr, *str;
643
644 JS_CHECK_RECURSION(cx, return JS_FALSE);
645
646 MUST_FLOW_THROUGH("out");
647 JS_PUSH_TEMP_ROOT(cx, 4, localroot, &tvr);
648
649 /* If outermost, we need parentheses to be an expression, not a block. */
650 outermost = (cx->sharpObjectMap.depth == 0);
651 obj = JS_THIS_OBJECT(cx, vp);
652 if (!obj || !(he = js_EnterSharpObject(cx, obj, &ida, &chars))) {
653 ok = JS_FALSE;
654 goto out;
655 }
656 if (IS_SHARP(he)) {
657 /*
658 * We didn't enter -- obj is already "sharp", meaning we've visited it
659 * already in our depth first search, and therefore chars contains a
660 * string of the form "#n#".
661 */
662 JS_ASSERT(!ida);
663 #if JS_HAS_SHARP_VARS
664 nchars = js_strlen(chars);
665 #else
666 chars[0] = '{';
667 chars[1] = '}';
668 chars[2] = 0;
669 nchars = 2;
670 #endif
671 goto make_string;
672 }
673 JS_ASSERT(ida);
674 ok = JS_TRUE;
675
676 if (!chars) {
677 /* If outermost, allocate 4 + 1 for "({})" and the terminator. */
678 chars = (jschar *) malloc(((outermost ? 4 : 2) + 1) * sizeof(jschar));
679 nchars = 0;
680 if (!chars)
681 goto error;
682 if (outermost)
683 chars[nchars++] = '(';
684 } else {
685 /* js_EnterSharpObject returned a string of the form "#n=" in chars. */
686 MAKE_SHARP(he);
687 nchars = js_strlen(chars);
688 chars = (jschar *)
689 realloc((ochars = chars), (nchars + 2 + 1) * sizeof(jschar));
690 if (!chars) {
691 free(ochars);
692 goto error;
693 }
694 if (outermost) {
695 /*
696 * No need for parentheses around the whole shebang, because #n=
697 * unambiguously begins an object initializer, and never a block
698 * statement.
699 */
700 outermost = JS_FALSE;
701 }
702 }
703
704 chars[nchars++] = '{';
705
706 comma = NULL;
707
708 /*
709 * We have four local roots for cooked and raw value GC safety. Hoist the
710 * "localroot + 2" out of the loop using the val local, which refers to
711 * the raw (unconverted, "uncooked") values.
712 */
713 val = localroot + 2;
714
715 for (i = 0, length = ida->length; i < length; i++) {
716 JSBool idIsLexicalIdentifier, needOldStyleGetterSetter;
717
718 /* Get strings for id and value and GC-root them via vp. */
719 id = ida->vector[i];
720
721 #if JS_HAS_GETTER_SETTER
722 ok = OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop);
723 if (!ok)
724 goto error;
725 #endif
726
727 /*
728 * Convert id to a jsval and then to a string. Decide early whether we
729 * prefer get/set or old getter/setter syntax.
730 */
731 idstr = js_ValueToString(cx, ID_TO_VALUE(id));
732 if (!idstr) {
733 ok = JS_FALSE;
734 OBJ_DROP_PROPERTY(cx, obj2, prop);
735 goto error;
736 }
737 *vp = STRING_TO_JSVAL(idstr); /* local root */
738 idIsLexicalIdentifier = js_IsIdentifier(idstr);
739 needOldStyleGetterSetter =
740 !idIsLexicalIdentifier ||
741 js_CheckKeyword(JSSTRING_CHARS(idstr),
742 JSSTRING_LENGTH(idstr)) != TOK_EOF;
743
744 #if JS_HAS_GETTER_SETTER
745
746 valcnt = 0;
747 if (prop) {
748 ok = OBJ_GET_ATTRIBUTES(cx, obj2, id, prop, &attrs);
749 if (!ok) {
750 OBJ_DROP_PROPERTY(cx, obj2, prop);
751 goto error;
752 }
753 if (OBJ_IS_NATIVE(obj2) &&
754 (attrs & (JSPROP_GETTER | JSPROP_SETTER))) {
755 if (attrs & JSPROP_GETTER) {
756 val[valcnt] = (jsval) ((JSScopeProperty *)prop)->getter;
757 gsopold[valcnt] =
758 ATOM_TO_STRING(cx->runtime->atomState.getterAtom);
759 gsop[valcnt] =
760 ATOM_TO_STRING(cx->runtime->atomState.getAtom);
761
762 valcnt++;
763 }
764 if (attrs & JSPROP_SETTER) {
765 val[valcnt] = (jsval) ((JSScopeProperty *)prop)->setter;
766 gsopold[valcnt] =
767 ATOM_TO_STRING(cx->runtime->atomState.setterAtom);
768 gsop[valcnt] =
769 ATOM_TO_STRING(cx->runtime->atomState.setAtom);
770
771 valcnt++;
772 }
773 } else {
774 valcnt = 1;
775 gsop[0] = NULL;
776 gsopold[0] = NULL;
777 ok = OBJ_GET_PROPERTY(cx, obj, id, &val[0]);
778 }
779 OBJ_DROP_PROPERTY(cx, obj2, prop);
780 }
781
782 #else /* !JS_HAS_GETTER_SETTER */
783
784 /*
785 * We simplify the source code at the price of minor dead code bloat in
786 * the ECMA version (for testing only, see jsversion.h). The null
787 * default values in gsop[j] suffice to disable non-ECMA getter and
788 * setter code.
789 */
790 valcnt = 1;
791 gsop[0] = NULL;
792 gsopold[0] = NULL;
793 ok = OBJ_GET_PROPERTY(cx, obj, id, &val[0]);
794
795 #endif /* !JS_HAS_GETTER_SETTER */
796
797 if (!ok)
798 goto error;
799
800 /*
801 * If id is a string that's not an identifier, then it needs to be
802 * quoted. Also, negative integer ids must be quoted.
803 */
804 if (JSID_IS_ATOM(id)
805 ? !idIsLexicalIdentifier
806 : (!JSID_IS_INT(id) || JSID_TO_INT(id) < 0)) {
807 idstr = js_QuoteString(cx, idstr, (jschar)'\'');
808 if (!idstr) {
809 ok = JS_FALSE;
810 goto error;
811 }
812 *vp = STRING_TO_JSVAL(idstr); /* local root */
813 }
814 JSSTRING_CHARS_AND_LENGTH(idstr, idstrchars, idstrlength);
815
816 for (j = 0; j < valcnt; j++) {
817 /* Convert val[j] to its canonical source form. */
818 valstr = js_ValueToSource(cx, val[j]);
819 if (!valstr) {
820 ok = JS_FALSE;
821 goto error;
822 }
823 localroot[j] = STRING_TO_JSVAL(valstr); /* local root */
824 JSSTRING_CHARS_AND_LENGTH(valstr, vchars, vlength);
825
826 if (vchars[0] == '#')
827 needOldStyleGetterSetter = JS_TRUE;
828
829 if (needOldStyleGetterSetter)
830 gsop[j] = gsopold[j];
831
832 /* If val[j] is a non-sharp object, consider sharpening it. */
833 vsharp = NULL;
834 vsharplength = 0;
835 #if JS_HAS_SHARP_VARS
836 if (!JSVAL_IS_PRIMITIVE(val[j]) && vchars[0] != '#') {
837 he = js_EnterSharpObject(cx, JSVAL_TO_OBJECT(val[j]), NULL,
838 &vsharp);
839 if (!he) {
840 ok = JS_FALSE;
841 goto error;
842 }
843 if (IS_SHARP(he)) {
844 vchars = vsharp;
845 vlength = js_strlen(vchars);
846 needOldStyleGetterSetter = JS_TRUE;
847 gsop[j] = gsopold[j];
848 } else {
849 if (vsharp) {
850 vsharplength = js_strlen(vsharp);
851 MAKE_SHARP(he);
852 needOldStyleGetterSetter = JS_TRUE;
853 gsop[j] = gsopold[j];
854 }
855 js_LeaveSharpObject(cx, NULL);
856 }
857 }
858 #endif
859
860 #ifndef OLD_GETTER_SETTER
861 /*
862 * Remove '(function ' from the beginning of valstr and ')' from the
863 * end so that we can put "get" in front of the function definition.
864 */
865 if (gsop[j] && VALUE_IS_FUNCTION(cx, val[j]) &&
866 !needOldStyleGetterSetter) {
867 JSFunction *fun = JS_ValueToFunction(cx, val[j]);
868 const jschar *start = vchars;
869 const jschar *end = vchars + vlength;
870
871 uint8 parenChomp = 0;
872 if (vchars[0] == '(') {
873 vchars++;
874 parenChomp = 1;
875 }
876
877 /*
878 * Try to jump "getter" or "setter" keywords, if we suspect
879 * they might appear here. This code can be confused by people
880 * defining Function.prototype.toString, so let's be cautious.
881 */
882 if (JSFUN_GETTER_TEST(fun->flags) ||
883 JSFUN_SETTER_TEST(fun->flags)) { /* skip "getter/setter" */
884 const jschar *tmp = js_strchr_limit(vchars, ' ', end);
885 if (tmp)
886 vchars = tmp + 1;
887 }
888
889 /* Try to jump "function" keyword. */
890 if (vchars)
891 vchars = js_strchr_limit(vchars, ' ', end);
892
893 if (vchars) {
894 if (*vchars == ' ')
895 vchars++;
896 vlength = end - vchars - parenChomp;
897 } else {
898 gsop[j] = NULL;
899 vchars = start;
900 }
901 }
902 #else
903 needOldStyleGetterSetter = JS_TRUE;
904 gsop[j] = gsopold[j];
905 #endif
906
907 #define SAFE_ADD(n) \
908 JS_BEGIN_MACRO \
909 size_t n_ = (n); \
910 curlen += n_; \
911 if (curlen < n_) \
912 goto overflow; \
913 JS_END_MACRO
914
915 curlen = nchars;
916 if (comma)
917 SAFE_ADD(2);
918 SAFE_ADD(idstrlength + 1);
919 if (gsop[j])
920 SAFE_ADD(JSSTRING_LENGTH(gsop[j]) + 1);
921 SAFE_ADD(vsharplength);
922 SAFE_ADD(vlength);
923 /* Account for the trailing null. */
924 SAFE_ADD((outermost ? 2 : 1) + 1);
925 #undef SAFE_ADD
926
927 if (curlen > (size_t)-1 / sizeof(jschar))
928 goto overflow;
929
930 /* Allocate 1 + 1 at end for closing brace and terminating 0. */
931 chars = (jschar *)
932 realloc((ochars = chars), curlen * sizeof(jschar));
933 if (!chars) {
934 /* Save code space on error: let JS_free ignore null vsharp. */
935 JS_free(cx, vsharp);
936 free(ochars);
937 goto error;
938 }
939
940 if (comma) {
941 chars[nchars++] = comma[0];
942 chars[nchars++] = comma[1];
943 }
944 comma = ", ";
945
946 if (needOldStyleGetterSetter) {
947 js_strncpy(&chars[nchars], idstrchars, idstrlength);
948 nchars += idstrlength;
949 if (gsop[j]) {
950 chars[nchars++] = ' ';
951 gsoplength = JSSTRING_LENGTH(gsop[j]);
952 js_strncpy(&chars[nchars], JSSTRING_CHARS(gsop[j]),
953 gsoplength);
954 nchars += gsoplength;
955 }
956 chars[nchars++] = ':';
957 } else { /* New style "decompilation" */
958 if (gsop[j]) {
959 gsoplength = JSSTRING_LENGTH(gsop[j]);
960 js_strncpy(&chars[nchars], JSSTRING_CHARS(gsop[j]),
961 gsoplength);
962 nchars += gsoplength;
963 chars[nchars++] = ' ';
964 }
965 js_strncpy(&chars[nchars], idstrchars, idstrlength);
966 nchars += idstrlength;
967 /* Extraneous space after id here will be extracted later */
968 chars[nchars++] = gsop[j] ? ' ' : ':';
969 }
970
971 if (vsharplength) {
972 js_strncpy(&chars[nchars], vsharp, vsharplength);
973 nchars += vsharplength;
974 }
975 js_strncpy(&chars[nchars], vchars, vlength);
976 nchars += vlength;
977
978 if (vsharp)
979 JS_free(cx, vsharp);
980 }
981 }
982
983 chars[nchars++] = '}';
984 if (outermost)
985 chars[nchars++] = ')';
986 chars[nchars] = 0;
987
988 error:
989 js_LeaveSharpObject(cx, &ida);
990
991 if (!ok) {
992 if (chars)
993 free(chars);
994 goto out;
995 }
996
997 if (!chars) {
998 JS_ReportOutOfMemory(cx);
999 ok = JS_FALSE;
1000 goto out;
1001 }
1002 make_string:
1003 str = js_NewString(cx, chars, nchars);
1004 if (!str) {
1005 free(chars);
1006 ok = JS_FALSE;
1007 goto out;
1008 }
1009 *vp = STRING_TO_JSVAL(str);
1010 ok = JS_TRUE;
1011 out:
1012 JS_POP_TEMP_ROOT(cx, &tvr);
1013 return ok;
1014
1015 overflow:
1016 JS_free(cx, vsharp);
1017 free(chars);
1018 chars = NULL;
1019 goto error;
1020 }
1021 #endif /* JS_HAS_TOSOURCE */
1022
1023 static JSBool
1024 obj_toString(JSContext *cx, uintN argc, jsval *vp)
1025 {
1026 JSObject *obj;
1027 jschar *chars;
1028 size_t nchars;
1029 const char *clazz, *prefix;
1030 JSString *str;
1031
1032 obj = JS_THIS_OBJECT(cx, vp);
1033 if (!obj)
1034 return JS_FALSE;
1035 obj = js_GetWrappedObject(cx, obj);
1036 clazz = OBJ_GET_CLASS(cx, obj)->name;
1037 nchars = 9 + strlen(clazz); /* 9 for "[object ]" */
1038 chars = (jschar *) JS_malloc(cx, (nchars + 1) * sizeof(jschar));
1039 if (!chars)
1040 return JS_FALSE;
1041
1042 prefix = "[object ";
1043 nchars = 0;
1044 while ((chars[nchars] = (jschar)*prefix) != 0)
1045 nchars++, prefix++;
1046 while ((chars[nchars] = (jschar)*clazz) != 0)
1047 nchars++, clazz++;
1048 chars[nchars++] = ']';
1049 chars[nchars] = 0;
1050
1051 str = js_NewString(cx, chars, nchars);
1052 if (!str) {
1053 JS_free(cx, chars);
1054 return JS_FALSE;
1055 }
1056 *vp = STRING_TO_JSVAL(str);
1057 return JS_TRUE;
1058 }
1059
1060 static JSBool
1061 obj_toLocaleString(JSContext *cx, uintN argc, jsval *vp)
1062 {
1063 jsval thisv;
1064 JSString *str;
1065
1066 thisv = JS_THIS(cx, vp);
1067 if (JSVAL_IS_NULL(thisv))
1068 return JS_FALSE;
1069
1070 str = js_ValueToString(cx, thisv);
1071 if (!str)
1072 return JS_FALSE;
1073
1074 *vp = STRING_TO_JSVAL(str);
1075 return JS_TRUE;
1076 }
1077
1078 static JSBool
1079 obj_valueOf(JSContext *cx, uintN argc, jsval *vp)
1080 {
1081 *vp = JS_THIS(cx, vp);
1082 return !JSVAL_IS_NULL(*vp);
1083 }
1084
1085 /*
1086 * Check whether principals subsumes scopeobj's principals, and return true
1087 * if so (or if scopeobj has no principals, for backward compatibility with
1088 * the JS API, which does not require principals), and false otherwise.
1089 */
1090 JSBool
1091 js_CheckPrincipalsAccess(JSContext *cx, JSObject *scopeobj,
1092 JSPrincipals *principals, JSAtom *caller)
1093 {
1094 JSSecurityCallbacks *callbacks;
1095 JSPrincipals *scopePrincipals;
1096 const char *callerstr;
1097
1098 callbacks = JS_GetSecurityCallbacks(cx);
1099 if (callbacks && callbacks->findObjectPrincipals) {
1100 scopePrincipals = callbacks->findObjectPrincipals(cx, scopeobj);
1101 if (!principals || !scopePrincipals ||
1102 !principals->subsume(principals, scopePrincipals)) {
1103 callerstr = js_AtomToPrintableString(cx, caller);
1104 if (!callerstr)
1105 return JS_FALSE;
1106 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
1107 JSMSG_BAD_INDIRECT_CALL, callerstr);
1108 return JS_FALSE;
1109 }
1110 }
1111 return JS_TRUE;
1112 }
1113
1114 JSObject *
1115 js_CheckScopeChainValidity(JSContext *cx, JSObject *scopeobj, const char *caller)
1116 {
1117 JSClass *clasp;
1118 JSExtendedClass *xclasp;
1119 JSObject *inner;
1120
1121 if (!scopeobj)
1122 goto bad;
1123
1124 OBJ_TO_INNER_OBJECT(cx, scopeobj);
1125 if (!scopeobj)
1126 return NULL;
1127
1128 inner = scopeobj;
1129
1130 /* XXX This is an awful gross hack. */
1131 while (scopeobj) {
1132 clasp = OBJ_GET_CLASS(cx, scopeobj);
1133 if (clasp->flags & JSCLASS_IS_EXTENDED) {
1134 xclasp = (JSExtendedClass*)clasp;
1135 if (xclasp->innerObject &&
1136 xclasp->innerObject(cx, scopeobj) != scopeobj) {
1137 goto bad;
1138 }
1139 }
1140
1141 scopeobj = OBJ_GET_PARENT(cx, scopeobj);
1142 }
1143
1144 return inner;
1145
1146 bad:
1147 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
1148 JSMSG_BAD_INDIRECT_CALL, caller);
1149 return NULL;
1150 }
1151
1152 const char *
1153 js_ComputeFilename(JSContext *cx, JSStackFrame *caller,
1154 JSPrincipals *principals, uintN *linenop)
1155 {
1156 uint32 flags;
1157 #ifdef DEBUG
1158 JSSecurityCallbacks *callbacks = JS_GetSecurityCallbacks(cx);
1159 #endif
1160
1161 JS_ASSERT(principals || !(callbacks && callbacks->findObjectPrincipals));
1162 flags = JS_GetScriptFilenameFlags(caller->script);
1163 if ((flags & JSFILENAME_PROTECTED) &&
1164 principals &&
1165 strcmp(principals->codebase, "[System Principal]")) {
1166 *linenop = 0;
1167 return principals->codebase;
1168 }
1169
1170 if (caller->regs && *caller->regs->pc == JSOP_EVAL) {
1171 JS_ASSERT(caller->regs->pc[JSOP_EVAL_LENGTH] == JSOP_LINENO);
1172 *linenop = GET_UINT16(caller->regs->pc + JSOP_EVAL_LENGTH);
1173 } else {
1174 *linenop = js_FramePCToLineNumber(cx, caller);
1175 }
1176 return caller->script->filename;
1177 }
1178
1179 static JSBool
1180 obj_eval(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1181 {
1182 JSStackFrame *fp, *caller;
1183 JSBool indirectCall;
1184 JSObject *scopeobj;
1185 JSString *str;
1186 const char *file;
1187 uintN line;
1188 JSPrincipals *principals;
1189 uint32 tcflags;
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
1197
1198 fp = cx->fp;
1199 caller = JS_GetScriptedCaller(cx, fp);
1200 indirectCall = (caller && caller->regs && *caller->regs->pc != JSOP_EVAL);
1201
1202 /*
1203 * Ban all indirect uses of eval (global.foo = eval; global.foo(...)) and
1204 * calls that attempt to use a non-global object as the "with" object in
1205 * the former indirect case.
1206 */
1207 scopeobj = OBJ_GET_PARENT(cx, obj);
1208 if (scopeobj) {
1209 scopeobj = js_GetWrappedObject(cx, obj);
1210 scopeobj = OBJ_GET_PARENT(cx, scopeobj);
1211 }
1212 if (indirectCall || scopeobj) {
1213 uintN flags = scopeobj
1214 ? JSREPORT_ERROR
1215 : JSREPORT_STRICT | JSREPORT_WARNING;
1216 if (!JS_ReportErrorFlagsAndNumber(cx, flags, js_GetErrorMessage, NULL,
1217 JSMSG_BAD_INDIRECT_CALL,
1218 js_eval_str)) {
1219 return JS_FALSE;
1220 }
1221 }
1222
1223 if (!JSVAL_IS_STRING(argv[0])) {
1224 *rval = argv[0];
1225 return JS_TRUE;
1226 }
1227
1228 /*
1229 * If the caller is a lightweight function and doesn't have a variables
1230 * object, then we need to provide one for the compiler to stick any
1231 * declared (var) variables into.
1232 */
1233 if (caller && !caller->varobj && !js_GetCallObject(cx, caller, NULL))
1234 return JS_FALSE;
1235
1236 /* eval no longer takes an optional trailing argument. */
1237 if (argc >= 2 &&
1238 !JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING | JSREPORT_STRICT,
1239 js_GetErrorMessage, NULL,
1240 JSMSG_EVAL_ARITY)) {
1241 return JS_FALSE;
1242 }
1243
1244 /* From here on, control must exit through label out with ok set. */
1245 MUST_FLOW_THROUGH("out");
1246 if (!scopeobj) {
1247 #if JS_HAS_EVAL_THIS_SCOPE
1248 /* If obj.eval(str), emulate 'with (obj) eval(str)' in the caller. */
1249 if (indirectCall) {
1250 callerScopeChain = js_GetScopeChain(cx, caller);
1251 if (!callerScopeChain) {
1252 ok = JS_FALSE;
1253 goto out;
1254 }
1255 OBJ_TO_INNER_OBJECT(cx, obj);
1256 if (!obj) {
1257 ok = JS_FALSE;
1258 goto out;
1259 }
1260 if (obj != callerScopeChain) {
1261 ok = js_CheckPrincipalsAccess(cx, obj,
1262 caller->script->principals,
1263 cx->runtime->atomState.evalAtom);
1264 if (!ok)
1265 goto out;
1266
1267 scopeobj = js_NewWithObject(cx, obj, callerScopeChain, -1);
1268 if (!scopeobj) {
1269 ok = JS_FALSE;
1270 goto out;
1271 }
1272
1273 /* Set fp->scopeChain too, for the compiler. */
1274 caller->scopeChain = fp->scopeChain = scopeobj;
1275
1276 /* Remember scopeobj so we can null its private when done. */
1277 setCallerScopeChain = scopeobj;
1278 }
1279
1280 callerVarObj = caller->varobj;
1281 if (obj != callerVarObj) {
1282 /* Set fp->varobj too, for the compiler. */
1283 caller->varobj = fp->varobj = obj;
1284 setCallerVarObj = JS_TRUE;
1285 }
1286 }
1287 #endif
1288
1289 /*
1290 * Compile using caller's current scope object.
1291 *
1292 * NB: This means that native callers (who reach this point through
1293 * the C API) must use the two parameter form.
1294 */
1295 if (caller) {
1296 scopeobj = js_GetScopeChain(cx, caller);
1297 if (!scopeobj) {
1298 ok = JS_FALSE;
1299 goto out;
1300 }
1301 }
1302 }
1303
1304 /* Ensure we compile this eval with the right object in the scope chain. */
1305 scopeobj = js_CheckScopeChainValidity(cx, scopeobj, js_eval_str);
1306 if (!scopeobj) {
1307 ok = JS_FALSE;
1308 goto out;
1309 }
1310
1311 str = JSVAL_TO_STRING(argv[0]);
1312 if (caller) {
1313 principals = JS_EvalFramePrincipals(cx, fp, caller);
1314 file = js_ComputeFilename(cx, caller, principals, &line);
1315 } else {
1316 file = NULL;
1317 line = 0;
1318 principals = NULL;
1319 }
1320
1321 tcflags = TCF_COMPILE_N_GO;
1322 if (caller)
1323 tcflags |= TCF_PUT_STATIC_DEPTH(caller->script->staticDepth + 1);
1324 script = js_CompileScript(cx, scopeobj, caller, principals, tcflags,
1325 JSSTRING_CHARS(str), JSSTRING_LENGTH(str),
1326 NULL, file, line);
1327 if (!script) {
1328 ok = JS_FALSE;
1329 goto out;
1330 }
1331
1332 if (argc < 2) {
1333 /* Execute using caller's new scope object (might be a Call object). */
1334 if (caller)
1335 scopeobj = caller->scopeChain;
1336 }
1337
1338 /*
1339 * Belt-and-braces: check that the lesser of eval's principals and the
1340 * caller's principals has access to scopeobj.
1341 */
1342 ok = js_CheckPrincipalsAccess(cx, scopeobj, principals,
1343 cx->runtime->atomState.evalAtom);
1344 if (ok)
1345 ok = js_Execute(cx, scopeobj, script, caller, JSFRAME_EVAL, rval);
1346
1347 script->u.nextToGC = JS_SCRIPTS_TO_GC(cx);
1348 JS_SCRIPTS_TO_GC(cx) = script;
1349 #ifdef CHECK_SCRIPT_OWNER
1350 script->owner = NULL;
1351 #endif
1352
1353 out:
1354 #if JS_HAS_EVAL_THIS_SCOPE
1355 /* Restore OBJ_GET_PARENT(scopeobj) not callerScopeChain in case of Call. */
1356 if (setCallerScopeChain) {
1357 caller->scopeChain = callerScopeChain;
1358 JS_ASSERT(OBJ_GET_CLASS(cx, setCallerScopeChain) == &js_WithClass);
1359 JS_SetPrivate(cx, setCallerScopeChain, NULL);
1360 }
1361 if (setCallerVarObj)
1362 caller->varobj = callerVarObj;
1363 #endif
1364 return ok;
1365 }
1366
1367 #if JS_HAS_OBJ_WATCHPOINT
1368
1369 static JSBool
1370 obj_watch_handler(JSContext *cx, JSObject *obj, jsval id, jsval old, jsval *nvp,
1371 void *closure)
1372 {
1373 JSObject *callable;
1374 JSSecurityCallbacks *callbacks;
1375 JSStackFrame *caller;
1376 JSPrincipals *subject, *watcher;
1377 JSResolvingKey key;
1378 JSResolvingEntry *entry;
1379 uint32 generation;
1380 jsval argv[3];
1381 JSBool ok;
1382
1383 callable = (JSObject *) closure;
1384
1385 callbacks = JS_GetSecurityCallbacks(cx);
1386 if (callbacks && callbacks->findObjectPrincipals) {
1387 /* Skip over any obj_watch_* frames between us and the real subject. */
1388 caller = JS_GetScriptedCaller(cx, cx->fp);
1389 if (caller) {
1390 /*
1391 * Only call the watch handler if the watcher is allowed to watch
1392 * the currently executing script.
1393 */
1394 watcher = callbacks->findObjectPrincipals(cx, callable);
1395 subject = JS_StackFramePrincipals(cx, caller);
1396
1397 if (watcher && subject && !watcher->subsume(watcher, subject)) {
1398 /* Silently don't call the watch handler. */
1399 return JS_TRUE;
1400 }
1401 }
1402 }
1403
1404 /* Avoid recursion on (obj, id) already being watched on cx. */
1405 key.obj = obj;
1406 key.id = id;
1407 if (!js_StartResolving(cx, &key, JSRESFLAG_WATCH, &entry))
1408 return JS_FALSE;
1409 if (!entry)
1410 return JS_TRUE;
1411 generation = cx->resolvingTable->generation;
1412
1413 argv[0] = id;
1414 argv[1] = old;
1415 argv[2] = *nvp;
1416 ok = js_InternalCall(cx, obj, OBJECT_TO_JSVAL(callable), 3, argv, nvp);
1417 js_StopResolving(cx, &key, JSRESFLAG_WATCH, entry, generation);
1418 return ok;
1419 }
1420
1421 static JSBool
1422 obj_watch(JSContext *cx, uintN argc, jsval *vp)
1423 {
1424 JSObject *callable;
1425 jsval userid, value;
1426 jsid propid;
1427 JSObject *obj;
1428 uintN attrs;
1429
1430 if (argc <= 1) {
1431 js_ReportMissingArg(cx, vp, 1);
1432 return JS_FALSE;
1433 }
1434
1435 callable = js_ValueToCallableObject(cx, &vp[3], 0);
1436 if (!callable)
1437 return JS_FALSE;
1438
1439 /* Compute the unique int/atom symbol id needed by js_LookupProperty. */
1440 userid = vp[2];
1441 if (!JS_ValueToId(cx, userid, &propid))
1442 return JS_FALSE;
1443
1444 obj = JS_THIS_OBJECT(cx, vp);
1445 if (!obj || !OBJ_CHECK_ACCESS(cx, obj, propid, JSACC_WATCH, &value, &attrs))
1446 return JS_FALSE;
1447 if (attrs & JSPROP_READONLY)
1448 return JS_TRUE;
1449 *vp = JSVAL_VOID;
1450
1451 if (OBJ_IS_DENSE_ARRAY(cx, obj) && !js_MakeArraySlow(cx, obj))
1452 return JS_FALSE;
1453 return JS_SetWatchPoint(cx, obj, userid, obj_watch_handler, callable);
1454 }
1455
1456 static JSBool
1457 obj_unwatch(JSContext *cx, uintN argc, jsval *vp)
1458 {
1459 JSObject *obj;
1460
1461 obj = JS_THIS_OBJECT(cx, vp);
1462 if (!obj)
1463 return JS_FALSE;
1464 *vp = JSVAL_VOID;
1465 return JS_ClearWatchPoint(cx, obj, argc != 0 ? vp[2] : JSVAL_VOID,
1466 NULL, NULL);
1467 }
1468
1469 #endif /* JS_HAS_OBJ_WATCHPOINT */
1470
1471 /*
1472 * Prototype and property query methods, to complement the 'in' and
1473 * 'instanceof' operators.
1474 */
1475
1476 /* Proposed ECMA 15.2.4.5. */
1477 static JSBool
1478 obj_hasOwnProperty(JSContext *cx, uintN argc, jsval *vp)
1479 {
1480 JSObject *obj;
1481
1482 obj = JS_THIS_OBJECT(cx, vp);
1483 return obj &&
1484 js_HasOwnPropertyHelper(cx, obj->map->ops->lookupProperty, argc, vp);
1485 }
1486
1487 JSBool
1488 js_HasOwnPropertyHelper(JSContext *cx, JSLookupPropOp lookup, uintN argc,
1489 jsval *vp)
1490 {
1491 jsid id;
1492 JSObject *obj;
1493
1494 if (!JS_ValueToId(cx, argc != 0 ? vp[2] : JSVAL_VOID, &id))
1495 return JS_FALSE;
1496 obj = JS_THIS_OBJECT(cx, vp);
1497 return obj && js_HasOwnProperty(cx, lookup, obj, id, vp);
1498 }
1499
1500 JSBool
1501 js_HasOwnProperty(JSContext *cx, JSLookupPropOp lookup, JSObject *obj, jsid id,
1502 jsval *vp)
1503 {
1504 JSObject *obj2;
1505 JSProperty *prop;
1506 JSScopeProperty *sprop;
1507
1508 if (!lookup(cx, obj, id, &obj2, &prop))
1509 return JS_FALSE;
1510 if (!prop) {
1511 *vp = JSVAL_FALSE;
1512 } else if (obj2 == obj) {
1513 *vp = JSVAL_TRUE;
1514 } else {
1515 JSClass *clasp;
1516 JSExtendedClass *xclasp;
1517 JSObject *outer;
1518
1519 clasp = OBJ_GET_CLASS(cx, obj2);
1520 if (!(clasp->flags & JSCLASS_IS_EXTENDED) ||
1521 !(xclasp = (JSExtendedClass *) clasp)->outerObject) {
1522 outer = NULL;
1523 } else {
1524 outer = xclasp->outerObject(cx, obj2);
1525 if (!outer)
1526 return JS_FALSE;
1527 }
1528 if (outer == obj) {
1529 *vp = JSVAL_TRUE;
1530 } else if (OBJ_IS_NATIVE(obj2) && OBJ_GET_CLASS(cx, obj) == clasp) {
1531 /*
1532 * The combination of JSPROP_SHARED and JSPROP_PERMANENT in a
1533 * delegated property makes that property appear to be direct in
1534 * all delegating instances of the same native class. This hack
1535 * avoids bloating every function instance with its own 'length'
1536 * (AKA 'arity') property. But it must not extend across class
1537 * boundaries, to avoid making hasOwnProperty lie (bug 320854).
1538 *
1539 * It's not really a hack, of course: a permanent property can't
1540 * be deleted, and JSPROP_SHARED means "don't allocate a slot in
1541 * any instance, prototype or delegating". Without a slot, and
1542 * without the ability to remove and recreate (with differences)
1543 * the property, there is no way to tell whether it is directly
1544 * owned, or indirectly delegated.
1545 */
1546 sprop = (JSScopeProperty *)prop;
1547 *vp = BOOLEAN_TO_JSVAL(SPROP_IS_SHARED_PERMANENT(sprop));
1548 } else {
1549 *vp = JSVAL_FALSE;
1550 }
1551 }
1552 if (prop)
1553 OBJ_DROP_PROPERTY(cx, obj2, prop);
1554 return JS_TRUE;
1555 }
1556
1557 #ifdef JS_TRACER
1558 static int32 FASTCALL
1559 Object_p_hasOwnProperty(JSContext* cx, JSObject* obj, JSString *str)
1560 {
1561 jsid id;
1562 jsval v;
1563
1564 if (!js_ValueToStringId(cx, STRING_TO_JSVAL(str), &id))
1565 return JSVAL_TO_BOOLEAN(JSVAL_VOID);
1566 if (!js_HasOwnProperty(cx, obj->map->ops->lookupProperty, obj, id, &v))
1567 return JSVAL_TO_BOOLEAN(JSVAL_VOID);
1568 JS_ASSERT(JSVAL_IS_BOOLEAN(v));
1569 return JSVAL_TO_BOOLEAN(v);
1570 }
1571 #endif
1572
1573 /* Proposed ECMA 15.2.4.6. */
1574 static JSBool
1575 obj_isPrototypeOf(JSContext *cx, uintN argc, jsval *vp)
1576 {
1577 JSBool b;
1578
1579 if (!js_IsDelegate(cx, JS_THIS_OBJECT(cx, vp),
1580 argc != 0 ? vp[2] : JSVAL_VOID, &b)) {
1581 return JS_FALSE;
1582 }
1583 *vp = BOOLEAN_TO_JSVAL(b);
1584 return JS_TRUE;
1585 }
1586
1587 /* Proposed ECMA 15.2.4.7. */
1588 static JSBool
1589 obj_propertyIsEnumerable(JSContext *cx, uintN argc, jsval *vp)
1590 {
1591 jsid id;
1592 JSObject *obj;
1593
1594 if (!JS_ValueToId(cx, argc != 0 ? vp[2] : JSVAL_VOID, &id))
1595 return JS_FALSE;
1596
1597 obj = JS_THIS_OBJECT(cx, vp);
1598 return obj && js_PropertyIsEnumerable(cx, obj, id, vp);
1599 }
1600
1601 #ifdef JS_TRACER
1602 static int32 FASTCALL
1603 Object_p_propertyIsEnumerable(JSContext* cx, JSObject* obj, JSString *str)
1604 {
1605 jsid id = ATOM_TO_JSID(STRING_TO_JSVAL(str));
1606 jsval v;
1607 if (!js_PropertyIsEnumerable(cx, obj, id, &v))
1608 return JSVAL_TO_BOOLEAN(JSVAL_VOID);
1609 JS_ASSERT(JSVAL_IS_BOOLEAN(v));
1610 return JSVAL_TO_BOOLEAN(v);
1611 }
1612 #endif
1613
1614 JSBool
1615 js_PropertyIsEnumerable(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
1616 {
1617 JSObject *pobj;
1618 uintN attrs;
1619 JSProperty *prop;
1620 JSBool ok;
1621
1622 if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &pobj, &prop))
1623 return JS_FALSE;
1624
1625 if (!prop) {
1626 *vp = JSVAL_FALSE;
1627 return JS_TRUE;
1628 }
1629
1630 /*
1631 * XXX ECMA spec error compatible: return false unless hasOwnProperty.
1632 * The ECMA spec really should be fixed so propertyIsEnumerable and the
1633 * for..in loop agree on whether prototype properties are enumerable,
1634 * obviously by fixing this method (not by breaking the for..in loop!).
1635 *
1636 * We check here for shared permanent prototype properties, which should
1637 * be treated as if they are local to obj. They are an implementation
1638 * technique used to satisfy ECMA requirements; users should not be able
1639 * to distinguish a shared permanent proto-property from a local one.
1640 */
1641 if (pobj != obj &&
1642 !(OBJ_IS_NATIVE(pobj) &&
1643 SPROP_IS_SHARED_PERMANENT((JSScopeProperty *)prop))) {
1644 OBJ_DROP_PROPERTY(cx, pobj, prop);
1645 *vp = JSVAL_FALSE;
1646 return JS_TRUE;
1647 }
1648
1649 ok = OBJ_GET_ATTRIBUTES(cx, pobj, id, prop, &attrs);
1650 OBJ_DROP_PROPERTY(cx, pobj, prop);
1651 if (ok)
1652 *vp = BOOLEAN_TO_JSVAL((attrs & JSPROP_ENUMERATE) != 0);
1653 return ok;
1654 }
1655
1656 #if JS_HAS_GETTER_SETTER
1657 static JSBool
1658 obj_defineGetter(JSContext *cx, uintN argc, jsval *vp)
1659 {
1660 jsval fval, junk;
1661 jsid id;
1662 JSObject *obj;
1663 uintN attrs;
1664
1665 if (argc <= 1 || JS_TypeOfValue(cx, vp[3]) != JSTYPE_FUNCTION) {
1666 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
1667 JSMSG_BAD_GETTER_OR_SETTER,
1668 js_getter_str);
1669 return JS_FALSE;
1670 }
1671 fval = vp[3];
1672
1673 if (!JS_ValueToId(cx, vp[2], &id))
1674 return JS_FALSE;
1675 obj = JS_THIS_OBJECT(cx, vp);
1676 if (!obj || !js_CheckRedeclaration(cx, obj, id, JSPROP_GETTER, NULL, NULL))
1677 return JS_FALSE;
1678 /*
1679 * Getters and setters are just like watchpoints from an access
1680 * control point of view.
1681 */
1682 if (!OBJ_CHECK_ACCESS(cx, obj, id, JSACC_WATCH, &junk, &attrs))
1683 return JS_FALSE;
1684 *vp = JSVAL_VOID;
1685 return OBJ_DEFINE_PROPERTY(cx, obj, id, JSVAL_VOID,
1686 (JSPropertyOp) JSVAL_TO_OBJECT(fval),
1687 JS_PropertyStub,
1688 JSPROP_ENUMERATE | JSPROP_GETTER | JSPROP_SHARED,
1689 NULL);
1690 }
1691
1692 static JSBool
1693 obj_defineSetter(JSContext *cx, uintN argc, jsval *vp)
1694 {
1695 jsval fval, junk;
1696 jsid id;
1697 JSObject *obj;
1698 uintN attrs;
1699
1700 if (argc <= 1 || JS_TypeOfValue(cx, vp[3]) != JSTYPE_FUNCTION) {
1701 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
1702 JSMSG_BAD_GETTER_OR_SETTER,
1703 js_setter_str);
1704 return JS_FALSE;
1705 }
1706 fval = vp[3];
1707
1708 if (!JS_ValueToId(cx, vp[2], &id))
1709 return JS_FALSE;
1710 obj = JS_THIS_OBJECT(cx, vp);
1711 if (!obj || !js_CheckRedeclaration(cx, obj, id, JSPROP_SETTER, NULL, NULL))
1712 return JS_FALSE;
1713 /*
1714 * Getters and setters are just like watchpoints from an access
1715 * control point of view.
1716 */
1717 if (!OBJ_CHECK_ACCESS(cx, obj, id, JSACC_WATCH, &junk, &attrs))
1718 return JS_FALSE;
1719 *vp = JSVAL_VOID;
1720 return OBJ_DEFINE_PROPERTY(cx, obj, id, JSVAL_VOID,
1721 JS_PropertyStub,
1722 (JSPropertyOp) JSVAL_TO_OBJECT(fval),
1723 JSPROP_ENUMERATE | JSPROP_SETTER | JSPROP_SHARED,
1724 NULL);
1725 }
1726
1727 static JSBool
1728 obj_lookupGetter(JSContext *cx, uintN argc, jsval *vp)
1729 {
1730 jsid id;
1731 JSObject *obj, *pobj;
1732 JSProperty *prop;
1733 JSScopeProperty *sprop;
1734
1735 if (!JS_ValueToId(cx, argc != 0 ? vp[2] : JSVAL_VOID, &id))
1736 return JS_FALSE;
1737 obj = JS_THIS_OBJECT(cx, vp);
1738 if (!obj || !OBJ_LOOKUP_PROPERTY(cx, obj, id, &pobj, &prop))
1739 return JS_FALSE;
1740 *vp = JSVAL_VOID;
1741 if (prop) {
1742 if (OBJ_IS_NATIVE(pobj)) {
1743 sprop = (JSScopeProperty *) prop;
1744 if (sprop->attrs & JSPROP_GETTER)
1745 *vp = OBJECT_TO_JSVAL(sprop->getter);
1746 }
1747 OBJ_DROP_PROPERTY(cx, pobj, prop);
1748 }
1749 return JS_TRUE;
1750 }
1751
1752 static JSBool
1753 obj_lookupSetter(JSContext *cx, uintN argc, jsval *vp)
1754 {
1755 jsid id;
1756 JSObject *obj, *pobj;
1757 JSProperty *prop;
1758 JSScopeProperty *sprop;
1759
1760 if (!JS_ValueToId(cx, argc != 0 ? vp[2] : JSVAL_VOID, &id))
1761 return JS_FALSE;
1762 obj = JS_THIS_OBJECT(cx, vp);
1763 if (!obj || !OBJ_LOOKUP_PROPERTY(cx, obj, id, &pobj, &prop))
1764 return JS_FALSE;
1765 *vp = JSVAL_VOID;
1766 if (prop) {
1767 if (OBJ_IS_NATIVE(pobj)) {
1768 sprop = (JSScopeProperty *) prop;
1769 if (sprop->attrs & JSPROP_SETTER)
1770 *vp = OBJECT_TO_JSVAL(sprop->setter);
1771 }
1772 OBJ_DROP_PROPERTY(cx, pobj, prop);
1773 }
1774 return JS_TRUE;
1775 }
1776 #endif /* JS_HAS_GETTER_SETTER */
1777
1778 JSBool
1779 obj_getPrototypeOf(JSContext *cx, uintN argc, jsval *vp)
1780 {
1781 JSObject *obj;
1782 uintN attrs;
1783
1784 if (argc == 0) {
1785 js_ReportMissingArg(cx, vp, 0);
1786 return JS_FALSE;
1787 }
1788
1789 obj = js_ValueToNonNullObject(cx, vp[2]);
1790 if (!obj)
1791 return JS_FALSE;
1792 vp[2] = OBJECT_TO_JSVAL(obj);
1793
1794 return OBJ_CHECK_ACCESS(cx, obj,
1795 ATOM_TO_JSID(cx->runtime->atomState.protoAtom),
1796 JSACC_PROTO, vp, &attrs);
1797 }
1798
1799 #if JS_HAS_OBJ_WATCHPOINT
1800 const char js_watch_str[] = "watch";
1801 const char js_unwatch_str[] = "unwatch";
1802 #endif
1803 const char js_hasOwnProperty_str[] = "hasOwnProperty";
1804 const char js_isPrototypeOf_str[] = "isPrototypeOf";
1805 const char js_propertyIsEnumerable_str[] = "propertyIsEnumerable";
1806 #if JS_HAS_GETTER_SETTER
1807 const char js_defineGetter_str[] = "__defineGetter__";
1808 const char js_defineSetter_str[] = "__defineSetter__";
1809 const char js_lookupGetter_str[] = "__lookupGetter__";
1810 const char js_lookupSetter_str[] = "__lookupSetter__";
1811 #endif
1812
1813 JS_DEFINE_TRCINFO_1(obj_hasOwnProperty,
1814 (3, (static, BOOL_FAIL, Object_p_hasOwnProperty, CONTEXT, THIS, STRING, 0, 0)))
1815 JS_DEFINE_TRCINFO_1(obj_propertyIsEnumerable,
1816 (3, (static, BOOL_FAIL, Object_p_propertyIsEnumerable, CONTEXT, THIS, STRING, 0, 0)))
1817
1818 static JSFunctionSpec object_methods[] = {
1819 #if JS_HAS_TOSOURCE
1820 JS_FN(js_toSource_str, obj_toSource, 0,0),
1821 #endif
1822 JS_FN(js_toString_str, obj_toString, 0,0),
1823 JS_FN(js_toLocaleString_str, obj_toLocaleString, 0,0),
1824 JS_FN(js_valueOf_str, obj_valueOf, 0,0),
1825 #if JS_HAS_OBJ_WATCHPOINT
1826 JS_FN(js_watch_str, obj_watch, 2,0),
1827 JS_FN(js_unwatch_str, obj_unwatch, 1,0),
1828 #endif
1829 JS_TN(js_hasOwnProperty_str, obj_hasOwnProperty, 1,0,
1830 obj_hasOwnProperty_trcinfo),
1831 JS_FN(js_isPrototypeOf_str, obj_isPrototypeOf, 1,0),
1832 JS_TN(js_propertyIsEnumerable_str, obj_propertyIsEnumerable, 1,0,
1833 obj_propertyIsEnumerable_trcinfo),
1834 #if JS_HAS_GETTER_SETTER
1835 JS_FN(js_defineGetter_str, obj_defineGetter, 2,0),
1836 JS_FN(js_defineSetter_str, obj_defineSetter, 2,0),
1837 JS_FN(js_lookupGetter_str, obj_lookupGetter, 1,0),
1838 JS_FN(js_lookupSetter_str, obj_lookupSetter, 1,0),
1839 #endif
1840 JS_FS_END
1841 };
1842
1843 static JSFunctionSpec object_static_methods[] = {
1844 JS_FN("getPrototypeOf", obj_getPrototypeOf, 1,0),
1845 JS_FS_END
1846 };
1847
1848 JSBool
1849 js_Object(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
1850 {
1851 if (argc == 0) {
1852 /* Trigger logic below to construct a blank object. */
1853 obj = NULL;
1854 } else {
1855 /* If argv[0] is null or undefined, obj comes back null. */
1856 if (!js_ValueToObject(cx, argv[0], &obj))
1857 return JS_FALSE;
1858 }
1859 if (!obj) {
1860 JS_ASSERT(!argc || JSVAL_IS_NULL(argv[0]) || JSVAL_IS_VOID(argv[0]));
1861 if (cx->fp->flags & JSFRAME_CONSTRUCTING)
1862 return JS_TRUE;
1863 obj = js_NewObject(cx, &js_ObjectClass, NULL, NULL, 0);
1864 if (!obj)
1865 return JS_FALSE;
1866 }
1867 *rval = OBJECT_TO_JSVAL(obj);
1868 return JS_TRUE;
1869 }
1870
1871 /*
1872 * ObjectOps and Class for with-statement stack objects.
1873 */
1874 static JSBool
1875 with_LookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
1876 JSProperty **propp)
1877 {
1878 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1879 if (!proto)
1880 return js_LookupProperty(cx, obj, id, objp, propp);
1881 return OBJ_LOOKUP_PROPERTY(cx, proto, id, objp, propp);
1882 }
1883
1884 static JSBool
1885 with_GetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
1886 {
1887 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1888 if (!proto)
1889 return js_GetProperty(cx, obj, id, vp);
1890 return OBJ_GET_PROPERTY(cx, proto, id, vp);
1891 }
1892
1893 static JSBool
1894 with_SetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
1895 {
1896 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1897 if (!proto)
1898 return js_SetProperty(cx, obj, id, vp);
1899 return OBJ_SET_PROPERTY(cx, proto, id, vp);
1900 }
1901
1902 static JSBool
1903 with_GetAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
1904 uintN *attrsp)
1905 {
1906 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1907 if (!proto)
1908 return js_GetAttributes(cx, obj, id, prop, attrsp);
1909 return OBJ_GET_ATTRIBUTES(cx, proto, id, prop, attrsp);
1910 }
1911
1912 static JSBool
1913 with_SetAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop,
1914 uintN *attrsp)
1915 {
1916 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1917 if (!proto)
1918 return js_SetAttributes(cx, obj, id, prop, attrsp);
1919 return OBJ_SET_ATTRIBUTES(cx, proto, id, prop, attrsp);
1920 }
1921
1922 static JSBool
1923 with_DeleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *rval)
1924 {
1925 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1926 if (!proto)
1927 return js_DeleteProperty(cx, obj, id, rval);
1928 return OBJ_DELETE_PROPERTY(cx, proto, id, rval);
1929 }
1930
1931 static JSBool
1932 with_DefaultValue(JSContext *cx, JSObject *obj, JSType hint, jsval *vp)
1933 {
1934 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1935 if (!proto)
1936 return js_DefaultValue(cx, obj, hint, vp);
1937 return OBJ_DEFAULT_VALUE(cx, proto, hint, vp);
1938 }
1939
1940 static JSBool
1941 with_Enumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
1942 jsval *statep, jsid *idp)
1943 {
1944 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1945 if (!proto)
1946 return js_Enumerate(cx, obj, enum_op, statep, idp);
1947 return OBJ_ENUMERATE(cx, proto, enum_op, statep, idp);
1948 }
1949
1950 static JSBool
1951 with_CheckAccess(JSContext *cx, JSObject *obj, jsid id, JSAccessMode mode,
1952 jsval *vp, uintN *attrsp)
1953 {
1954 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1955 if (!proto)
1956 return js_CheckAccess(cx, obj, id, mode, vp, attrsp);
1957 return OBJ_CHECK_ACCESS(cx, proto, id, mode, vp, attrsp);
1958 }
1959
1960 static JSObject *
1961 with_ThisObject(JSContext *cx, JSObject *obj)
1962 {
1963 JSObject *proto = OBJ_GET_PROTO(cx, obj);
1964 if (!proto)
1965 return obj;
1966 return OBJ_THIS_OBJECT(cx, proto);
1967 }
1968
1969 JS_FRIEND_DATA(JSObjectOps) js_WithObjectOps = {
1970 js_NewObjectMap, js_DestroyObjectMap,
1971 with_LookupProperty, js_DefineProperty,
1972 with_GetProperty, with_SetProperty,
1973 with_GetAttributes, with_SetAttributes,
1974 with_DeleteProperty, with_DefaultValue,
1975 with_Enumerate, with_CheckAccess,
1976 with_ThisObject, NATIVE_DROP_PROPERTY,
1977 NULL, NULL,
1978 NULL, NULL,
1979 js_SetProtoOrParent, js_SetProtoOrParent,
1980 js_TraceObject, js_Clear,
1981 NULL, NULL
1982 };
1983
1984 static JSObjectOps *
1985 with_getObjectOps(JSContext *cx, JSClass *clasp)
1986 {
1987 return &js_WithObjectOps;
1988 }
1989
1990 JSClass js_WithClass = {
1991 "With",
1992 JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_IS_ANONYMOUS,
1993 JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
1994 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
1995 with_getObjectOps,
1996 0,0,0,0,0,0,0
1997 };
1998
1999 JSObject *
2000 js_NewWithObject(JSContext *cx, JSObject *proto, JSObject *parent, jsint depth)
2001 {
2002 JSObject *obj;
2003
2004 obj = js_NewObject(cx, &js_WithClass, proto, parent, 0);
2005 if (!obj)
2006 return NULL;
2007 STOBJ_SET_SLOT(obj, JSSLOT_PRIVATE, PRIVATE_TO_JSVAL(cx->fp));
2008 OBJ_SET_BLOCK_DEPTH(cx, obj, depth);
2009 return obj;
2010 }
2011
2012 JSObject *
2013 js_NewBlockObject(JSContext *cx)
2014 {
2015 JSObject *obj;
2016 JSBool ok;
2017
2018 /*
2019 * Null obj's proto slot so that Object.prototype.* does not pollute block
2020 * scopes. Make sure obj has its own scope too, since clearing proto does
2021 * not affect OBJ_SCOPE(obj).
2022 */
2023 obj = js_NewObject(cx, &js_BlockClass, NULL, NULL, 0);
2024 if (!obj)
2025 return NULL;
2026 JS_LOCK_OBJ(cx, obj);
2027 ok = js_GetMutableScope(cx, obj) != NULL;
2028 JS_UNLOCK_OBJ(cx, obj);
2029 if (!ok)
2030 return NULL;
2031 OBJ_CLEAR_PROTO(cx, obj);
2032 return obj;
2033 }
2034
2035 JSObject *
2036 js_CloneBlockObject(JSContext *cx, JSObject *proto, JSObject *parent,
2037 JSStackFrame *fp)
2038 {
2039 JSObject *clone;
2040
2041 JS_ASSERT(STOBJ_GET_CLASS(proto) == &js_BlockClass);
2042 JS_ASSERT(!OBJ_IS_CLONED_BLOCK(proto));
2043 clone = js_NewObject(cx, &js_BlockClass, proto, parent, 0);
2044 if (!clone)
2045 return NULL;
2046 STOBJ_SET_SLOT(clone, JSSLOT_PRIVATE, PRIVATE_TO_JSVAL(fp));
2047 STOBJ_SET_SLOT(clone, JSSLOT_BLOCK_DEPTH,
2048 OBJ_GET_SLOT(cx, proto, JSSLOT_BLOCK_DEPTH));
2049 JS_ASSERT(OBJ_IS_CLONED_BLOCK(clone));
2050 return clone;
2051 }
2052
2053 JSBool
2054 js_PutBlockObject(JSContext *cx, JSBool normalUnwind)
2055 {
2056 JSStackFrame *fp;
2057 JSObject *obj;
2058 uintN depth, count;
2059
2060 /* Blocks have one fixed slot available for the first local.*/
2061 JS_STATIC_ASSERT(JS_INITIAL_NSLOTS == JSSLOT_BLOCK_DEPTH + 2);
2062
2063 fp = cx->fp;
2064 obj = fp->scopeChain;
2065 JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_BlockClass);
2066 JS_ASSERT(OBJ_GET_PRIVATE(cx, obj) == cx->fp);
2067 JS_ASSERT(OBJ_IS_CLONED_BLOCK(obj));
2068
2069 /*
2070 * Block objects should never be exposed to scripts. Thus the clone should
2071 * not own the property map and rather always share it with the prototype
2072 * object. This allows to skip updating OBJ_SCOPE(obj)->map.freeslot after
2073 * we copy the stack slots into reserved slots.
2074 */
2075 JS_ASSERT(OBJ_SCOPE(obj)->object != obj);
2076
2077 /* Block objects should not have reserved slots before they are put. */
2078 JS_ASSERT(STOBJ_NSLOTS(obj) == JS_INITIAL_NSLOTS);
2079
2080 /* The block and its locals must be on the current stack for GC safety. */
2081 depth = OBJ_BLOCK_DEPTH(cx, obj);
2082 count = OBJ_BLOCK_COUNT(cx, obj);
2083 JS_ASSERT(depth <= (size_t) (fp->regs->sp - StackBase(fp)));
2084 JS_ASSERT(count <= (size_t) (fp->regs->sp - StackBase(fp) - depth));
2085
2086 /* See comments in CheckDestructuring from jsparse.c. */
2087 JS_ASSERT(count >= 1);
2088
2089 depth += fp->script->nfixed;
2090 obj->fslots[JSSLOT_BLOCK_DEPTH + 1] = fp->slots[depth];
2091 if (normalUnwind && count > 1) {
2092 --count;
2093 JS_LOCK_OBJ(cx, obj);
2094 if (!js_ReallocSlots(cx, obj, JS_INITIAL_NSLOTS + count, JS_TRUE))
2095 normalUnwind = JS_FALSE;
2096 else
2097 memcpy(obj->dslots, fp->slots + depth + 1, count * sizeof(jsval));
2098 JS_UNLOCK_OBJ(cx, obj);
2099 }
2100
2101 /* We must clear the private slot even with errors. */
2102 JS_SetPrivate(cx, obj, NULL);
2103 fp->scopeChain = OBJ_GET_PARENT(cx, obj);
2104 return normalUnwind;
2105 }
2106
2107 static JSBool
2108 block_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
2109 {
2110 uintN index;
2111 JSStackFrame *fp;
2112
2113 JS_ASSERT(JS_InstanceOf(cx, obj, &js_BlockClass, NULL));
2114 JS_ASSERT(OBJ_IS_CLONED_BLOCK(obj));
2115 if (!JSVAL_IS_INT(id))
2116 return JS_TRUE;
2117
2118 index = (uint16) JSVAL_TO_INT(id);
2119 fp = (JSStackFrame *) JS_GetPrivate(cx, obj);
2120 if (fp) {
2121 index += fp->script->nfixed + OBJ_BLOCK_DEPTH(cx, obj);
2122 JS_ASSERT(index < fp->script->nslots);
2123 *vp = fp->slots[index];
2124 return JS_TRUE;
2125 }
2126
2127 /* Reserve slots start with the first slot after the private. */
2128 index += JSSLOT_BLOCK_DEPTH - JSSLOT_PRIVATE;
2129 return JS_GetReservedSlot(cx, obj, index, vp);
2130 }
2131
2132 static JSBool
2133 block_setProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
2134 {
2135 uintN index;
2136 JSStackFrame *fp;
2137
2138 JS_ASSERT(JS_InstanceOf(cx, obj, &js_BlockClass, NULL));
2139 if (!JSVAL_IS_INT(id))
2140 return JS_TRUE;
2141
2142 index = (uint16) JSVAL_TO_INT(id);
2143 fp = (JSStackFrame *) JS_GetPrivate(cx, obj);
2144 if (fp) {
2145 index += fp->script->nfixed + OBJ_BLOCK_DEPTH(cx, obj);
2146 JS_ASSERT(index < fp->script->nslots);
2147 fp->slots[index] = *vp;
2148 return JS_TRUE;
2149 }
2150
2151 /* Reserve slots start with the first slot after the private. */
2152 index += JSSLOT_BLOCK_DEPTH - JSSLOT_PRIVATE;
2153 return JS_SetReservedSlot(cx, obj, index, *vp);
2154 }
2155
2156 #if JS_HAS_XDR
2157
2158 #define NO_PARENT_INDEX ((uint32)-1)
2159
2160 uint32
2161 FindObjectIndex(JSObjectArray *array, JSObject *obj)
2162 {
2163 size_t i;
2164
2165 if (array) {
2166 i = array->length;
2167 do {
2168
2169 if (array->vector[--i] == obj)
2170 return i;
2171 } while (i != 0);
2172 }
2173
2174 return NO_PARENT_INDEX;
2175 }
2176
2177 static JSBool
2178 block_xdrObject(JSXDRState *xdr, JSObject **objp)
2179 {
2180 JSContext *cx;
2181 uint32 parentId;
2182 JSObject *obj, *parent;
2183 uint16 depth, count, i;
2184 uint32 tmp;
2185 JSTempValueRooter tvr;
2186 JSScopeProperty *sprop;
2187 jsid propid;
2188 JSAtom *atom;
2189 int16 shortid;
2190 JSBool ok;
2191
2192 cx = xdr->cx;
2193 #ifdef __GNUC__
2194 obj = NULL; /* quell GCC overwarning */
2195 #endif
2196
2197 if (xdr->mode == JSXDR_ENCODE) {
2198 obj = *objp;
2199 parent = OBJ_GET_PARENT(cx, obj);
2200 parentId = (xdr->script->objectsOffset == 0)
2201 ? NO_PARENT_INDEX
2202 : FindObjectIndex(JS_SCRIPT_OBJECTS(xdr->script), parent);
2203 depth = (uint16)OBJ_BLOCK_DEPTH(cx, obj);
2204 count = (uint16)OBJ_BLOCK_COUNT(cx, obj);
2205 tmp = (uint32)(depth << 16) | count;
2206 }
2207 #ifdef __GNUC__ /* suppress bogus gcc warnings */
2208 else count = 0;
2209 #endif
2210
2211 /* First, XDR the parent atomid. */
2212 if (!JS_XDRUint32(xdr, &parentId))
2213 return JS_FALSE;
2214
2215 if (xdr->mode == JSXDR_DECODE) {
2216 obj = js_NewBlockObject(cx);
2217 if (!obj)
2218 return JS_FALSE;
2219 *objp = obj;
2220
2221 /*
2222 * If there's a parent id, then get the parent out of our script's
2223 * object array. We know that we XDR block object in outer-to-inner
2224 * order, which means that getting the parent now will work.
2225 */
2226 if (parentId == NO_PARENT_INDEX)
2227 parent = NULL;
2228 else
2229 JS_GET_SCRIPT_OBJECT(xdr->script, parentId, parent);
2230 STOBJ_SET_PARENT(obj, parent);
2231 }
2232
2233 JS_PUSH_SINGLE_TEMP_ROOT(cx, OBJECT_TO_JSVAL(obj), &tvr);
2234
2235 if (!JS_XDRUint32(xdr, &tmp)) {
2236 JS_POP_TEMP_ROOT(cx, &tvr);
2237 return JS_FALSE;
2238 }
2239
2240 if (xdr->mode == JSXDR_DECODE) {
2241 depth = (uint16)(tmp >> 16);
2242 count = (uint16)tmp;
2243 STOBJ_SET_SLOT(obj, JSSLOT_BLOCK_DEPTH, INT_TO_JSVAL(depth));
2244 }
2245
2246 /*
2247 * XDR the block object's properties. We know that there are 'count'
2248 * properties to XDR, stored as id/shortid pairs. We do not XDR any
2249 * non-native properties, only those that the compiler created.
2250 */
2251 sprop = NULL;
2252 ok = JS_TRUE;
2253 for (i = 0; i < count; i++) {
2254 if (xdr->mode == JSXDR_ENCODE) {
2255 /* Find a property to XDR. */
2256 do {
2257 /* If sprop is NULL, this is the first property. */
2258 sprop = sprop ? sprop->parent : OBJ_SCOPE(obj)->lastProp;
2259 } while (!(sprop->flags & SPROP_HAS_SHORTID));
2260
2261 JS_ASSERT(sprop->getter == js_BlockClass.getProperty);
2262 propid = sprop->id;
2263 JS_ASSERT(JSID_IS_ATOM(propid));
2264 atom = JSID_TO_ATOM(propid);
2265 shortid = sprop->shortid;
2266 JS_ASSERT(shortid >= 0);
2267 }
2268
2269 /* XDR the real id, then the shortid. */
2270 if (!js_XDRStringAtom(xdr, &atom) ||
2271 !JS_XDRUint16(xdr, (uint16 *)&shortid)) {
2272 ok = JS_FALSE;
2273 break;
2274 }
2275
2276 if (xdr->mode == JSXDR_DECODE) {
2277 if (!js_DefineNativeProperty(cx, obj, ATOM_TO_JSID(atom),
2278 JSVAL_VOID, NULL, NULL,
2279 JSPROP_ENUMERATE | JSPROP_PERMANENT,
2280 SPROP_HAS_SHORTID, shortid, NULL)) {
2281 ok = JS_FALSE;
2282 break;
2283 }
2284 }
2285 }
2286
2287 JS_POP_TEMP_ROOT(cx, &tvr);
2288 return ok;
2289 }
2290
2291 #else
2292 # define block_xdrObject NULL
2293 #endif
2294
2295 static uint32
2296 block_reserveSlots(JSContext *cx, JSObject *obj)
2297 {
2298 return OBJ_IS_CLONED_BLOCK(obj) ? OBJ_BLOCK_COUNT(cx, obj) : 0;
2299 }
2300
2301 JSClass js_BlockClass = {
2302 "Block",
2303 JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1) |
2304 JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_CACHED_PROTO(JSProto_Block),
2305 JS_PropertyStub, JS_PropertyStub, block_getProperty, block_setProperty,
2306 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
2307 NULL, NULL, NULL, NULL, block_xdrObject, NULL, NULL, block_reserveSlots
2308 };
2309
2310 JSObject*
2311 js_InitBlockClass(JSContext *cx, JSObject* obj)
2312 {
2313 JSObject *proto;
2314
2315 proto = JS_InitClass(cx, obj, NULL, &js_BlockClass, NULL, 0, NULL,
2316 NULL, NULL, NULL);
2317 if (!proto)
2318 return NULL;
2319
2320 OBJ_CLEAR_PROTO(cx, proto);
2321 return proto;
2322 }
2323
2324 JSObject *
2325 js_InitEval(JSContext *cx, JSObject *obj)
2326 {
2327 /* ECMA (15.1.2.1) says 'eval' is a property of the global object. */
2328 if (!js_DefineFunction(cx, obj, cx->runtime->atomState.evalAtom,
2329 obj_eval, 1, 0)) {
2330 return NULL;
2331 }
2332
2333 return obj;
2334 }
2335
2336 JSObject *
2337 js_InitObjectClass(JSContext *cx, JSObject *obj)
2338 {
2339 return JS_InitClass(cx, obj, NULL, &js_ObjectClass, js_Object, 1,
2340 object_props, object_methods, NULL,
2341 object_static_methods);
2342 }
2343
2344 void
2345 js_InitObjectMap(JSObjectMap *map, jsrefcount nrefs, JSObjectOps *ops,
2346 JSClass *clasp)
2347 {
2348 map->nrefs = nrefs;
2349 map->ops = ops;
2350 map->freeslot = JSSLOT_FREE(clasp);
2351 }
2352
2353 JSObjectMap *
2354 js_NewObjectMap(JSContext *cx, jsrefcount nrefs, JSObjectOps *ops,
2355 JSClass *clasp, JSObject *obj)
2356 {
2357 return (JSObjectMap *) js_NewScope(cx, nrefs, ops, clasp, obj);
2358 }
2359
2360 void
2361 js_DestroyObjectMap(JSContext *cx, JSObjectMap *map)
2362 {
2363 js_DestroyScope(cx, (JSScope *)map);
2364 }
2365
2366 JSObjectMap *
2367 js_HoldObjectMap(JSContext *cx, JSObjectMap *map)
2368 {
2369 JS_ASSERT(map->nrefs >= 0);
2370 JS_ATOMIC_INCREMENT(&map->nrefs);
2371 return map;
2372 }
2373
2374 JSObjectMap *
2375 js_DropObjectMap(JSContext *cx, JSObjectMap *map, JSObject *obj)
2376 {
2377 JS_ASSERT(map->nrefs > 0);
2378 JS_ATOMIC_DECREMENT(&map->nrefs);
2379 if (map->nrefs == 0) {
2380 map->ops->destroyObjectMap(cx, map);
2381 return NULL;
2382 }
2383 if (MAP_IS_NATIVE(map) && ((JSScope *)map)->object == obj)
2384 ((JSScope *)map)->object = NULL;
2385 return map;
2386 }
2387
2388 static void
2389 FreeSlots(JSContext *cx, JSObject *obj)
2390 {
2391 if (obj->dslots) {
2392 JS_ASSERT((uint32)obj->dslots[-1] > JS_INITIAL_NSLOTS);
2393 JS_free(cx, obj->dslots - 1);
2394 obj->dslots = NULL;
2395 }
2396 }
2397
2398 #define SLOTS_TO_DYNAMIC_WORDS(nslots) \
2399 (JS_ASSERT((nslots) > JS_INITIAL_NSLOTS), (nslots) + 1 - JS_INITIAL_NSLOTS)
2400
2401 #define DYNAMIC_WORDS_TO_SLOTS(words) \
2402 (JS_ASSERT((words) > 1), (words) - 1 + JS_INITIAL_NSLOTS)
2403
2404 JSBool
2405 js_ReallocSlots(JSContext *cx, JSObject *obj, uint32 nslots,
2406 JSBool exactAllocation)
2407 {
2408 jsval *old, *slots;
2409 uint32 oslots, nwords, owords, log, i;
2410
2411 /*
2412 * Minimal number of dynamic slots to allocate.
2413 */
2414 #define MIN_DYNAMIC_WORDS 4
2415
2416 /*
2417 * The limit to switch to linear allocation strategy from the power of 2
2418 * growth no to waste too much memory.
2419 */
2420 #define LINEAR_GROWTH_STEP JS_BIT(16)
2421
2422 old = obj->dslots;
2423 if (nslots <= JS_INITIAL_NSLOTS) {
2424 if (old &&
2425 (exactAllocation ||
2426 SLOTS_TO_DYNAMIC_WORDS((uint32)old[-1]) != MIN_DYNAMIC_WORDS ||
2427 nslots <= (JS_INITIAL_NSLOTS +
2428 JSSLOT_FREE(STOBJ_GET_CLASS(obj))) / 2)) {
2429 /*
2430 * We do not want to free dynamic slots when allocation is a hint,
2431 * we reached minimal allocation and almost all fixed slots are
2432 * used. It avoids allocating dynamic slots again when properties
2433 * are added to the object.
2434 *
2435 * If there were no private or reserved slots, the condition to
2436 * free the slots would be
2437 *
2438 * nslots <= JS_INITIAL_NSLOTS / 2
2439 *
2440 * but to account for never removed slots before JSSLOT_FREE(class)
2441 * we need to subtract it from the slot counts which gives
2442 *
2443 * nslots - JSSLOT_FREE <= (JS_INITIAL_NSLOTS - JSSLOT_FREE) / 2
2444 *
2445 * or
2446 *
2447 * nslots <= (JS_INITIAL_NSLOTS + JSSLOT_FREE) / 2
2448 */
2449 FreeSlots(cx, obj);
2450 }
2451 return JS_TRUE;
2452 }
2453
2454 oslots = (old) ? (uint32)*--old : JS_INITIAL_NSLOTS;
2455 nwords = SLOTS_TO_DYNAMIC_WORDS(nslots);
2456
2457 if (nslots > oslots) {
2458 if (!exactAllocation) {
2459 /*
2460 * Round up nslots so the number of bytes in dslots array is power
2461 * of 2 to ensure exponential grouth.
2462 */
2463 if (nwords <= MIN_DYNAMIC_WORDS) {
2464 nwords = MIN_DYNAMIC_WORDS;
2465 } else if (nwords < LINEAR_GROWTH_STEP) {
2466 JS_CEILING_LOG2(log, nwords);
2467 nwords = JS_BIT(log);
2468 } else {
2469 nwords = JS_ROUNDUP(nwords, LINEAR_GROWTH_STEP);
2470 }
2471 }
2472 slots = (jsval *)JS_realloc(cx, old, nwords * sizeof(jsval));
2473 if (!slots)
2474 return JS_FALSE;
2475 } else {
2476 JS_ASSERT(nslots < oslots);
2477 if (!exactAllocation) {
2478 owords = DYNAMIC_WORDS_TO_SLOTS(oslots);
2479 if (owords <= MIN_DYNAMIC_WORDS)
2480 return JS_TRUE;
2481 if (owords < LINEAR_GROWTH_STEP * 2) {
2482 /*
2483 * Shrink only if 1/4 of slots are left and we need to grow
2484 * the array at least twice to reach the current capacity. It
2485 * prevents frequent capacity growth/shrinking when slots are
2486 * often removed and added.
2487 */
2488 if (nwords > owords / 4)
2489 return JS_TRUE;
2490 JS_CEILING_LOG2(log, nwords);
2491 nwords = JS_BIT(log);
2492 if (nwords < MIN_DYNAMIC_WORDS)
2493 nwords = MIN_DYNAMIC_WORDS;
2494 } else {
2495 /*
2496 * Shrink only if we free at least 2 linear allocation
2497 * segments, to prevent growth/shrinking resonance.
2498 */
2499 if (nwords > owords - LINEAR_GROWTH_STEP * 2)
2500 return JS_TRUE;
2501 nwords = JS_ROUNDUP(nwords, LINEAR_GROWTH_STEP);
2502 }
2503 }
2504
2505 /* We avoid JS_realloc not to report a failed shrink attempt. */
2506 slots = (jsval *)realloc(old, nwords * sizeof(jsval));
2507 if (!slots)
2508 slots = old;
2509 }
2510
2511 nslots = DYNAMIC_WORDS_TO_SLOTS(nwords);
2512 *slots++ = (jsval)nslots;
2513 obj->dslots = slots;
2514
2515 /* If we're extending an allocation, initialize free slots. */
2516 for (i = oslots; i < nslots; i++)
2517 slots[i - JS_INITIAL_NSLOTS] = JSVAL_VOID;
2518
2519 return JS_TRUE;
2520
2521 #undef LINEAR_GROWTH_STEP
2522 #undef MIN_DYNAMIC_WORDS
2523 }
2524
2525 extern JSBool
2526 js_GetClassId(JSContext *cx, JSClass *clasp, jsid *idp)
2527 {
2528 JSProtoKey key;
2529 JSAtom *atom;
2530
2531 key = JSCLASS_CACHED_PROTO_KEY(clasp);
2532 if (key != JSProto_Null) {
2533 *idp = INT_TO_JSID(key);
2534 } else if (clasp->flags & JSCLASS_IS_ANONYMOUS) {
2535 *idp = INT_TO_JSID(JSProto_Object);
2536 } else {
2537 atom = js_Atomize(cx, clasp->name, strlen(clasp->name), 0);
2538 if (!atom)
2539 return JS_FALSE;
2540 *idp = ATOM_TO_JSID(atom);
2541 }
2542 return JS_TRUE;
2543 }
2544
2545 JSObject *
2546 js_NewObject(JSContext *cx, JSClass *clasp, JSObject *proto, JSObject *parent,
2547 uintN objectSize)
2548 {
2549 jsid id;
2550
2551 /* Bootstrap the ur-object, and make it the default prototype object. */
2552 if (!proto) {
2553 if (!js_GetClassId(cx, clasp, &id))
2554 return NULL;
2555 if (!js_GetClassPrototype(cx, parent, id, &proto))
2556 return NULL;
2557 if (!proto &&
2558 !js_GetClassPrototype(cx, parent, INT_TO_JSID(JSProto_Object),
2559 &proto)) {
2560 return NULL;
2561 }
2562 }
2563
2564 return js_NewObjectWithGivenProto(cx, clasp, proto, parent, objectSize);
2565 }
2566
2567 JSObject *
2568 js_NewObjectWithGivenProto(JSContext *cx, JSClass *clasp, JSObject *proto,
2569 JSObject *parent, uintN objectSize)
2570 {
2571 JSObject *obj;
2572 JSObjectOps *ops;
2573 JSObjectMap *map;
2574 JSClass *protoclasp;
2575 uint32 nslots, i;
2576 JSTempValueRooter tvr;
2577
2578 #ifdef INCLUDE_MOZILLA_DTRACE
2579 if (JAVASCRIPT_OBJECT_CREATE_START_ENABLED())
2580 jsdtrace_object_create_start(cx->fp, clasp);
2581 #endif
2582
2583 /* Currently only functions can have non-standard allocation size. */
2584 if (clasp == &js_FunctionClass) {
2585 if (objectSize == 0)
2586 objectSize = sizeof(JSFunction);
2587 else
2588 JS_ASSERT(objectSize == sizeof(JSObject));
2589 } else {
2590 JS_ASSERT(objectSize == 0);
2591 objectSize = sizeof(JSObject);
2592 }
2593
2594 /*
2595 * Allocate an object from the GC heap and initialize all its fields before
2596 * doing any operation that can potentially trigger GC.
2597 */
2598 obj = (JSObject *) js_NewGCThing(cx, GCX_OBJECT, objectSize);
2599 if (!obj)
2600 goto earlybad;
2601
2602 obj->map = NULL;
2603 obj->dslots = NULL;
2604
2605 /*
2606 * Set the class slot with the initial value of the system and delegate
2607 * flags set to false.
2608 */
2609 JS_ASSERT(((jsuword) clasp & 3) == 0);
2610 obj->classword = jsuword(clasp);
2611 JS_ASSERT(!STOBJ_IS_DELEGATE(obj));
2612 JS_ASSERT(!STOBJ_IS_SYSTEM(obj));
2613
2614 /* Set the proto and parent properties. */
2615 STOBJ_SET_PROTO(obj, proto);
2616 STOBJ_SET_PARENT(obj, parent);
2617
2618 /* Initialize the remaining fixed slots. */
2619 for (i = JSSLOT_PRIVATE; i != JS_INITIAL_NSLOTS; ++i)
2620 obj->fslots[i] = JSVAL_VOID;
2621
2622 #ifdef DEBUG
2623 memset((uint8 *) obj + sizeof(JSObject), JS_FREE_PATTERN,
2624 objectSize - sizeof(JSObject));
2625 #endif
2626
2627 /*
2628 * Root obj to prevent it from being collected out from under this call to
2629 * js_NewObject. There's a possibilty of GC under the objectHook call-out
2630 * further below.
2631 */
2632 JS_PUSH_TEMP_ROOT_OBJECT(cx, obj, &tvr);
2633
2634 /* Always call the class's getObjectOps hook if it has one. */
2635 ops = clasp->getObjectOps
2636 ? clasp->getObjectOps(cx, clasp)
2637 : &js_ObjectOps;
2638
2639 /*
2640 * Default parent to the parent of the prototype, which was set from
2641 * the parent of the prototype's constructor.
2642 */
2643 if (proto && !parent)
2644 STOBJ_SET_PARENT(obj, OBJ_GET_PARENT(cx, proto));
2645
2646 /*
2647 * Share proto's map only if it has the same JSObjectOps, and only if
2648 * proto's class has the same private and reserved slots as obj's map
2649 * and class have. We assume that if prototype and object are of the
2650 * same class, they always have the same number of computed reserved
2651 * slots (returned via clasp->reserveSlots); otherwise, prototype and
2652 * object classes must have the same (null or not) reserveSlots hook.
2653 */
2654 if (proto &&
2655 (map = proto->map)->ops == ops &&
2656 ((protoclasp = OBJ_GET_CLASS(cx, proto)) == clasp ||
2657 (!((protoclasp->flags ^ clasp->flags) &
2658 (JSCLASS_HAS_PRIVATE |
2659 (JSCLASS_RESERVED_SLOTS_MASK << JSCLASS_RESERVED_SLOTS_SHIFT))) &&
2660 protoclasp->reserveSlots == clasp->reserveSlots)))
2661 {
2662 /* Share the given prototype's map. */
2663 obj->map = js_HoldObjectMap(cx, map);
2664 } else {
2665 map = ops->newObjectMap(cx, 1, ops, clasp, obj);
2666 if (!map)
2667 goto bad;
2668 obj->map = map;
2669
2670 /* Let ops->newObjectMap set freeslot so as to reserve slots. */
2671 nslots = map->freeslot;
2672 JS_ASSERT(nslots >= JSSLOT_PRIVATE);
2673 if (nslots > JS_INITIAL_NSLOTS &&
2674 !js_ReallocSlots(cx, obj, nslots, JS_TRUE)) {
2675 js_DropObjectMap(cx, map, obj);
2676 obj->map = NULL;
2677 goto bad;
2678 }
2679 }
2680
2681 if (cx->debugHooks->objectHook) {
2682 JS_KEEP_ATOMS(cx->runtime);
2683 cx->debugHooks->objectHook(cx, obj, JS_TRUE,
2684 cx->debugHooks->objectHookData);
2685 JS_UNKEEP_ATOMS(cx->runtime);
2686 }
2687
2688 out:
2689 JS_POP_TEMP_ROOT(cx, &tvr);
2690 cx->weakRoots.newborn[GCX_OBJECT] = obj;
2691 #ifdef INCLUDE_MOZILLA_DTRACE
2692 if (JAVASCRIPT_OBJECT_CREATE_ENABLED())
2693 jsdtrace_object_create(cx, clasp, obj);
2694 if (JAVASCRIPT_OBJECT_CREATE_DONE_ENABLED())
2695 jsdtrace_object_create_done(cx->fp, clasp);
2696 #endif
2697 return obj;
2698
2699 bad:
2700 obj = NULL;
2701 goto out;
2702
2703 earlybad:
2704 #ifdef INCLUDE_MOZILLA_DTRACE
2705 if (JAVASCRIPT_OBJECT_CREATE_ENABLED())
2706 jsdtrace_object_create(cx, clasp, NULL);
2707 if (JAVASCRIPT_OBJECT_CREATE_DONE_ENABLED())
2708 jsdtrace_object_create_done(cx->fp, clasp);
2709 #endif
2710 return NULL;
2711 }
2712
2713 JS_BEGIN_EXTERN_C
2714
2715 static JSObject *
2716 js_InitNullClass(JSContext *cx, JSObject *obj)
2717 {
2718 JS_ASSERT(0);
2719 return NULL;
2720 }
2721
2722 #define JS_PROTO(name,code,init) extern JSObject *init(JSContext *, JSObject *);
2723 #include "jsproto.tbl"
2724 #undef JS_PROTO
2725
2726 static JSObjectOp lazy_prototype_init[JSProto_LIMIT] = {
2727 #define JS_PROTO(name,code,init) init,
2728 #include "jsproto.tbl"
2729 #undef JS_PROTO
2730 };
2731
2732 JS_END_EXTERN_C
2733
2734 JSBool
2735 js_GetClassObject(JSContext *cx, JSObject *obj, JSProtoKey key,
2736 JSObject **objp)
2737 {
2738 JSBool ok;
2739 JSObject *tmp, *cobj;
2740 JSResolvingKey rkey;
2741 JSResolvingEntry *rentry;
2742 uint32 generation;
2743 JSObjectOp init;
2744 jsval v;
2745
2746 while ((tmp = OBJ_GET_PARENT(cx, obj)) != NULL)
2747 obj = tmp;
2748 if (!(OBJ_GET_CLASS(cx, obj)->flags & JSCLASS_IS_GLOBAL)) {
2749 *objp = NULL;
2750 return JS_TRUE;
2751 }
2752
2753 ok = JS_GetReservedSlot(cx, obj, key, &v);
2754 if (!ok)
2755 return JS_FALSE;
2756 if (!JSVAL_IS_PRIMITIVE(v)) {
2757 *objp = JSVAL_TO_OBJECT(v);
2758 return JS_TRUE;
2759 }
2760
2761 rkey.obj = obj;
2762 rkey.id = ATOM_TO_JSID(cx->runtime->atomState.classAtoms[key]);
2763 if (!js_StartResolving(cx, &rkey, JSRESFLAG_LOOKUP, &rentry))
2764 return JS_FALSE;
2765 if (!rentry) {
2766 /* Already caching key in obj -- suppress recursion. */
2767 *objp = NULL;
2768 return JS_TRUE;
2769 }
2770 generation = cx->resolvingTable->generation;
2771
2772 cobj = NULL;
2773 init = lazy_prototype_init[key];
2774 if (init) {
2775 if (!init(cx, obj)) {
2776 ok = JS_FALSE;
2777 } else {
2778 ok = JS_GetReservedSlot(cx, obj, key, &v);
2779 if (ok && !JSVAL_IS_PRIMITIVE(v))
2780 cobj = JSVAL_TO_OBJECT(v);
2781 }
2782 }
2783
2784 js_StopResolving(cx, &rkey, JSRESFLAG_LOOKUP, rentry, generation);
2785 *objp = cobj;
2786 return ok;
2787 }
2788
2789 JSBool
2790 js_SetClassObject(JSContext *cx, JSObject *obj, JSProtoKey key, JSObject *cobj)
2791 {
2792 JS_ASSERT(!OBJ_GET_PARENT(cx, obj));
2793 if (!(OBJ_GET_CLASS(cx, obj)->flags & JSCLASS_IS_GLOBAL))
2794 return JS_TRUE;
2795
2796 return JS_SetReservedSlot(cx, obj, key, OBJECT_TO_JSVAL(cobj));
2797 }
2798
2799 JSBool
2800 js_FindClassObject(JSContext *cx, JSObject *start, jsid id, jsval *vp)
2801 {
2802 JSObject *obj, *cobj, *pobj;
2803 JSProtoKey key;
2804 JSProperty *prop;
2805 jsval v;
2806 JSScopeProperty *sprop;
2807
2808 if (start || (cx->fp && (start = cx->fp->scopeChain) != NULL)) {
2809 /* Find the topmost object in the scope chain. */
2810 do {
2811 obj = start;
2812 start = OBJ_GET_PARENT(cx, obj);
2813 } while (start);
2814 } else {
2815 obj = cx->globalObject;
2816 if (!obj) {
2817 *vp = JSVAL_VOID;
2818 return JS_TRUE;
2819 }
2820 }
2821
2822 OBJ_TO_INNER_OBJECT(cx, obj);
2823 if (!obj)
2824 return JS_FALSE;
2825
2826 if (JSID_IS_INT(id)) {
2827 key = (JSProtoKey) JSID_TO_INT(id);
2828 JS_ASSERT(key != JSProto_Null);
2829 if (!js_GetClassObject(cx, obj, key, &cobj))
2830 return JS_FALSE;
2831 if (cobj) {
2832 *vp = OBJECT_TO_JSVAL(cobj);
2833 return JS_TRUE;
2834 }
2835 id = ATOM_TO_JSID(cx->runtime->atomState.classAtoms[key]);
2836 }
2837
2838 JS_ASSERT(OBJ_IS_NATIVE(obj));
2839 if (js_LookupPropertyWithFlags(cx, obj, id, JSRESOLVE_CLASSNAME,
2840 &pobj, &prop) < 0) {
2841 return JS_FALSE;
2842 }
2843 v = JSVAL_VOID;
2844 if (prop) {
2845 if (OBJ_IS_NATIVE(pobj)) {
2846 sprop = (JSScopeProperty *) prop;
2847 if (SPROP_HAS_VALID_SLOT(sprop, OBJ_SCOPE(pobj))) {
2848 v = LOCKED_OBJ_GET_SLOT(pobj, sprop->slot);
2849 if (JSVAL_IS_PRIMITIVE(v))
2850 v = JSVAL_VOID;
2851 }
2852 }
2853 OBJ_DROP_PROPERTY(cx, pobj, prop);
2854 }
2855 *vp = v;
2856 return JS_TRUE;
2857 }
2858
2859 JSObject *
2860 js_ConstructObject(JSContext *cx, JSClass *clasp, JSObject *proto,
2861 JSObject *parent, uintN argc, jsval *argv)
2862 {
2863 jsid id;
2864 jsval cval, rval;
2865 JSTempValueRooter argtvr, tvr;
2866 JSObject *obj, *ctor;
2867
2868 JS_PUSH_TEMP_ROOT(cx, argc, argv, &argtvr);
2869
2870 if (!js_GetClassId(cx, clasp, &id) ||
2871 !js_FindClassObject(cx, parent, id, &cval)) {
2872 JS_POP_TEMP_ROOT(cx, &argtvr);
2873 return NULL;
2874 }
2875
2876 if (JSVAL_IS_PRIMITIVE(cval)) {
2877 js_ReportIsNotFunction(cx, &cval, JSV2F_CONSTRUCT | JSV2F_SEARCH_STACK);
2878 JS_POP_TEMP_ROOT(cx, &argtvr);
2879 return NULL;
2880 }
2881
2882 /*
2883 * Protect cval in case a crazy getter for .prototype uproots it. After
2884 * this point, all control flow must exit through label out with obj set.
2885 */
2886 JS_PUSH_SINGLE_TEMP_ROOT(cx, cval, &tvr);
2887 MUST_FLOW_THROUGH("out");
2888
2889 /*
2890 * If proto or parent are NULL, set them to Constructor.prototype and/or
2891 * Constructor.__parent__, just like JSOP_NEW does.
2892 */
2893 ctor = JSVAL_TO_OBJECT(cval);
2894 if (!parent)
2895 parent = OBJ_GET_PARENT(cx, ctor);
2896 if (!proto) {
2897 if (!OBJ_GET_PROPERTY(cx, ctor,
2898 ATOM_TO_JSID(cx->runtime->atomState
2899 .classPrototypeAtom),
2900 &rval)) {
2901 obj = NULL;
2902 goto out;
2903 }
2904 if (JSVAL_IS_OBJECT(rval))
2905 proto = JSVAL_TO_OBJECT(rval);
2906 }
2907
2908 obj = js_NewObject(cx, clasp, proto, parent, 0);
2909 if (!obj)
2910 goto out;
2911
2912 if (!js_InternalConstruct(cx, obj, cval, argc, argv, &rval))
2913 goto bad;
2914
2915 if (JSVAL_IS_PRIMITIVE(rval))
2916 goto out;
2917 obj = JSVAL_TO_OBJECT(rval);
2918
2919 /*
2920 * If the instance's class differs from what was requested, throw a type
2921 * error. If the given class has both the JSCLASS_HAS_PRIVATE and the
2922 * JSCLASS_CONSTRUCT_PROTOTYPE flags, and the instance does not have its
2923 * private data set at this point, then the constructor was replaced and
2924 * we should throw a type error.
2925 */
2926 if (OBJ_GET_CLASS(cx, obj) != clasp ||
2927 (!(~clasp->flags & (JSCLASS_HAS_PRIVATE |
2928 JSCLASS_CONSTRUCT_PROTOTYPE)) &&
2929 !JS_GetPrivate(cx, obj))) {
2930 JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
2931 JSMSG_WRONG_CONSTRUCTOR, clasp->name);
2932 goto bad;
2933 }
2934
2935 out:
2936 JS_POP_TEMP_ROOT(cx, &tvr);
2937 JS_POP_TEMP_ROOT(cx, &argtvr);
2938 return obj;
2939
2940 bad:
2941 cx->weakRoots.newborn[GCX_OBJECT] = NULL;
2942 obj = NULL;
2943 goto out;
2944 }
2945
2946 void
2947 js_FinalizeObject(JSContext *cx, JSObject *obj)
2948 {
2949 JSObjectMap *map;
2950
2951 /* Cope with stillborn objects that have no map. */
2952 map = obj->map;
2953 if (!map)
2954 return;
2955
2956 if (cx->debugHooks->objectHook) {
2957 cx->debugHooks->objectHook(cx, obj, JS_FALSE,
2958 cx->debugHooks->objectHookData);
2959 }
2960
2961 /* Finalize obj first, in case it needs map and slots. */
2962 STOBJ_GET_CLASS(obj)->finalize(cx, obj);
2963
2964 #ifdef INCLUDE_MOZILLA_DTRACE
2965 if (JAVASCRIPT_OBJECT_FINALIZE_ENABLED())
2966 jsdtrace_object_finalize(obj);
2967 #endif
2968
2969 /* Drop map and free slots. */
2970 js_DropObjectMap(cx, map, obj);
2971 FreeSlots(cx, obj);
2972 }
2973
2974 /* XXXbe if one adds props, deletes earlier props, adds more, the last added
2975 won't recycle the deleted props' slots. */
2976 JSBool
2977 js_AllocSlot(JSContext *cx, JSObject *obj, uint32 *slotp)
2978 {
2979 JSObjectMap *map;
2980 JSClass *clasp;
2981
2982 map = obj->map;
2983 JS_ASSERT(!MAP_IS_NATIVE(map) || ((JSScope *)map)->object == obj);
2984 clasp = LOCKED_OBJ_GET_CLASS(obj);
2985 if (map->freeslot == JSSLOT_FREE(clasp) && clasp->reserveSlots) {
2986 /* Adjust map->freeslot to include computed reserved slots, if any. */
2987 map->freeslot += clasp->reserveSlots(cx, obj);
2988 }
2989
2990 if (map->freeslot >= STOBJ_NSLOTS(obj) &&
2991 !js_ReallocSlots(cx, obj, map->freeslot + 1, JS_FALSE)) {
2992 return JS_FALSE;
2993 }
2994
2995 /* js_ReallocSlots or js_FreeSlot should set the free slots to void. */
2996 JS_ASSERT(JSVAL_IS_VOID(STOBJ_GET_SLOT(obj, map->freeslot)));
2997 *slotp = map->freeslot++;
2998 return JS_TRUE;
2999 }
3000
3001 void
3002 js_FreeSlot(JSContext *cx, JSObject *obj, uint32 slot)
3003 {
3004 JSObjectMap *map;
3005
3006 map = obj->map;
3007 JS_ASSERT(!MAP_IS_NATIVE(map) || ((JSScope *)map)->object == obj);
3008 LOCKED_OBJ_SET_SLOT(obj, slot, JSVAL_VOID);
3009 if (map->freeslot == slot + 1) {
3010 map->freeslot = slot;
3011
3012 /* When shrinking, js_ReallocSlots always returns true. */
3013 js_ReallocSlots(cx, obj, slot, JS_FALSE);
3014 }
3015 }
3016
3017 jsid
3018 js_CheckForStringIndex(jsid id, const jschar *cp, const jschar *end,
3019 JSBool negative)
3020 {
3021 jsuint index = JS7_UNDEC(*cp++);
3022 jsuint oldIndex = 0;
3023 jsuint c = 0;
3024
3025 if (index != 0) {
3026 while (JS7_ISDEC(*cp)) {
3027 oldIndex = index;
3028 c = JS7_UNDEC(*cp);
3029 index = 10 * index + c;
3030 cp++;
3031 }
3032 }
3033 if (cp == end &&
3034 (oldIndex < (JSVAL_INT_MAX / 10) ||
3035 (oldIndex == (JSVAL_INT_MAX / 10) &&
3036 c <= (JSVAL_INT_MAX % 10)))) {
3037 if (negative)
3038 index = 0 - index;
3039 id = INT_TO_JSID((jsint)index);
3040 }
3041 return id;
3042 }
3043
3044 static JSBool
3045 PurgeProtoChain(JSContext *cx, JSObject *obj, jsid id)
3046 {
3047 JSScope *scope;
3048 JSScopeProperty *sprop;
3049
3050 while (obj) {
3051 if (!OBJ_IS_NATIVE(obj)) {
3052 obj = OBJ_GET_PROTO(cx, obj);
3053 continue;
3054 }
3055 JS_LOCK_OBJ(cx, obj);
3056 scope = OBJ_SCOPE(obj);
3057 sprop = SCOPE_GET_PROPERTY(scope, id);
3058 if (sprop) {
3059 PCMETER(JS_PROPERTY_CACHE(cx).pcpurges++);
3060 SCOPE_MAKE_UNIQUE_SHAPE(cx, scope);
3061 JS_UNLOCK_SCOPE(cx, scope);
3062 return JS_TRUE;
3063 }
3064 obj = LOCKED_OBJ_GET_PROTO(scope->object);
3065 JS_UNLOCK_SCOPE(cx, scope);
3066 }
3067 return JS_FALSE;
3068 }
3069
3070 static void
3071 PurgeScopeChain(JSContext *cx, JSObject *obj, jsid id)
3072 {
3073 if (!OBJ_IS_DELEGATE(cx, obj))
3074 return;
3075
3076 PurgeProtoChain(cx, OBJ_GET_PROTO(cx, obj), id);
3077 while ((obj = OBJ_GET_PARENT(cx, obj)) != NULL) {
3078 if (PurgeProtoChain(cx, obj, id))
3079 return;
3080 }
3081 }
3082
3083 JSScopeProperty *
3084 js_AddNativeProperty(JSContext *cx, JSObject *obj, jsid id,
3085 JSPropertyOp getter, JSPropertyOp setter, uint32 slot,
3086 uintN attrs, uintN flags, intN shortid)
3087 {
3088 JSScope *scope;
3089 JSScopeProperty *sprop;
3090
3091 /*
3092 * Purge the property cache of now-shadowed id in obj's scope chain. Do
3093 * this optimistically (assuming no failure below) before locking obj, so
3094 * we can lock the shadowed scope.
3095 */
3096 PurgeScopeChain(cx, obj, id);
3097
3098 JS_LOCK_OBJ(cx, obj);
3099 scope = js_GetMutableScope(cx, obj);
3100 if (!scope) {
3101 sprop = NULL;
3102 } else {
3103 /* Convert string indices to integers if appropriate. */
3104 CHECK_FOR_STRING_INDEX(id);
3105 sprop = js_AddScopeProperty(cx, scope, id, getter, setter, slot, attrs,
3106 flags, shortid);
3107 }
3108 JS_UNLOCK_OBJ(cx, obj);
3109 return sprop;
3110 }
3111
3112 JSScopeProperty *
3113 js_ChangeNativePropertyAttrs(JSContext *cx, JSObject *obj,
3114 JSScopeProperty *sprop, uintN attrs, uintN mask,
3115 JSPropertyOp getter, JSPropertyOp setter)
3116 {
3117 JSScope *scope;
3118
3119 JS_LOCK_OBJ(cx, obj);
3120 scope = js_GetMutableScope(cx, obj);
3121 if (!scope) {
3122 sprop = NULL;
3123 } else {
3124 sprop = js_ChangeScopePropertyAttrs(cx, scope, sprop, attrs, mask,
3125 getter, setter);
3126 }
3127 JS_UNLOCK_OBJ(cx, obj);
3128 return sprop;
3129 }
3130
3131 JSBool
3132 js_DefineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value,
3133 JSPropertyOp getter, JSPropertyOp setter, uintN attrs,
3134 JSProperty **propp)
3135 {
3136 return js_DefineNativeProperty(cx, obj, id, value, getter, setter, attrs,
3137 0, 0, propp);
3138 }
3139
3140 /*
3141 * Backward compatibility requires allowing addProperty hooks to mutate the
3142 * nominal initial value of a slot-full property, while GC safety wants that
3143 * value to be stored before the call-out through the hook. Optimize to do
3144 * both while saving cycles for classes that stub their addProperty hook.
3145 */
3146 #define ADD_PROPERTY_HELPER(cx,clasp,obj,scope,sprop,vp,cleanup) \
3147 JS_BEGIN_MACRO \
3148 if ((clasp)->addProperty != JS_PropertyStub) { \
3149 jsval nominal_ = *(vp); \
3150 if (!(clasp)->addProperty(cx, obj, SPROP_USERID(sprop), vp)) { \
3151 cleanup; \
3152 } \
3153 if (*(vp) != nominal_) { \
3154 if (SPROP_HAS_VALID_SLOT(sprop, scope)) \
3155 LOCKED_OBJ_WRITE_BARRIER(cx, obj, (sprop)->slot, *(vp)); \
3156 } \
3157 } \
3158 JS_END_MACRO
3159
3160 JSBool
3161 js_DefineNativeProperty(JSContext *cx, JSObject *obj, jsid id, jsval value,
3162 JSPropertyOp getter, JSPropertyOp setter, uintN attrs,
3163 uintN flags, intN shortid, JSProperty **propp)
3164 {
3165 JSClass *clasp;
3166 JSScope *scope;
3167 JSScopeProperty *sprop;
3168
3169 /* Convert string indices to integers if appropriate. */
3170 CHECK_FOR_STRING_INDEX(id);
3171
3172 #if JS_HAS_GETTER_SETTER
3173 /*
3174 * If defining a getter or setter, we must check for its counterpart and
3175 * update the attributes and property ops. A getter or setter is really
3176 * only half of a property.
3177 */
3178 sprop = NULL;
3179 if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) {
3180 JSObject *pobj;
3181 JSProperty *prop;
3182
3183 /*
3184 * If JS_THREADSAFE and id is found, js_LookupProperty returns with
3185 * sprop non-null and pobj locked. If pobj == obj, the property is
3186 * already in obj and obj has its own (mutable) scope. So if we are
3187 * defining a getter whose setter was already defined, or vice versa,
3188 * finish the job via js_ChangeScopePropertyAttributes, and refresh
3189 * the property cache line for (obj, id) to map sprop.
3190 */
3191 if (!js_LookupProperty(cx, obj, id, &pobj, &prop))
3192 return JS_FALSE;
3193 sprop = (JSScopeProperty *) prop;
3194 if (sprop &&
3195 pobj == obj &&
3196 (sprop->attrs & (JSPROP_GETTER | JSPROP_SETTER))) {
3197 sprop = js_ChangeScopePropertyAttrs(cx, OBJ_SCOPE(obj), sprop,
3198 attrs, sprop->attrs,
3199 (attrs & JSPROP_GETTER)
3200 ? getter
3201 : sprop->getter,
3202 (attrs & JSPROP_SETTER)
3203 ? setter
3204 : sprop->setter);
3205
3206 /* NB: obj == pobj, so we can share unlock code at the bottom. */
3207 if (!sprop)
3208 goto bad;
3209 } else if (prop) {
3210 /* NB: call OBJ_DROP_PROPERTY, as pobj might not be native. */
3211 OBJ_DROP_PROPERTY(cx, pobj, prop);
3212 prop = NULL;
3213 sprop = NULL;
3214 }
3215 }
3216 #endif /* JS_HAS_GETTER_SETTER */
3217
3218 /*
3219 * Purge the property cache of now-shadowed id in obj's scope chain.
3220 * Do this early, before locking obj to avoid nesting locks.
3221 */
3222 PurgeScopeChain(cx, obj, id);
3223
3224 /* Lock if object locking is required by this implementation. */
3225 JS_LOCK_OBJ(cx, obj);
3226
3227 /* Use the object's class getter and setter by default. */
3228 clasp = LOCKED_OBJ_GET_CLASS(obj);
3229 if (!getter)
3230 getter = clasp->getProperty;
3231 if (!setter)
3232 setter = clasp->setProperty;
3233
3234 /* Get obj's own scope if it has one, or create a new one for obj. */
3235 scope = js_GetMutableScope(cx, obj);
3236 if (!scope)
3237 goto bad;
3238
3239 if (!sprop) {
3240 /* Add a new property, or replace an existing one of the same id. */
3241 if (clasp->flags & JSCLASS_SHARE_ALL_PROPERTIES)
3242 attrs |= JSPROP_SHARED;
3243 sprop = js_AddScopeProperty(cx, scope, id, getter, setter,
3244 SPROP_INVALID_SLOT, attrs, flags,
3245 shortid);
3246 if (!sprop)
3247 goto bad;
3248 }
3249
3250 /* Store value before calling addProperty, in case the latter GC's. */
3251 if (SPROP_HAS_VALID_SLOT(sprop, scope))
3252 LOCKED_OBJ_WRITE_BARRIER(cx, obj, sprop->slot, value);
3253
3254 /* XXXbe called with lock held */
3255 ADD_PROPERTY_HELPER(cx, clasp, obj, scope, sprop, &value,
3256 js_RemoveScopeProperty(cx, scope, id);
3257 goto bad);
3258
3259 if (propp)
3260 *propp = (JSProperty *) sprop;
3261 else
3262 JS_UNLOCK_OBJ(cx, obj);
3263 return JS_TRUE;
3264
3265 bad:
3266 JS_UNLOCK_OBJ(cx, obj);
3267 return JS_FALSE;
3268 }
3269
3270 /*
3271 * Given pc pointing after a property accessing bytecode, return true if the
3272 * access is "object-detecting" in the sense used by web scripts, e.g., when
3273 * checking whether document.all is defined.
3274 */
3275 static JSBool
3276 Detecting(JSContext *cx, jsbytecode *pc)
3277 {
3278 JSScript *script;
3279 jsbytecode *endpc;
3280 JSOp op;
3281 JSAtom *atom;
3282
3283 if (!cx->fp)
3284 return JS_FALSE;
3285 script = cx->fp->script;
3286 for (endpc = script->code + script->length;
3287 pc < endpc;
3288 pc += js_CodeSpec[op].length) {
3289 /* General case: a branch or equality op follows the access. */
3290 op = (JSOp) *pc;
3291 if (js_CodeSpec[op].format & JOF_DETECTING)
3292 return JS_TRUE;
3293
3294 switch (op) {
3295 case JSOP_NULL:
3296 /*
3297 * Special case #1: handle (document.all == null). Don't sweat
3298 * about JS1.2's revision of the equality operators here.
3299 */
3300 if (++pc < endpc)
3301 return *pc == JSOP_EQ || *pc == JSOP_NE;
3302 return JS_FALSE;
3303
3304 case JSOP_NAME:
3305 /*
3306 * Special case #2: handle (document.all == undefined). Don't
3307 * worry about someone redefining undefined, which was added by
3308 * Edition 3, so is read/write for backward compatibility.
3309 */
3310 GET_ATOM_FROM_BYTECODE(script, pc, 0, atom);
3311 if (atom == cx->runtime->atomState.typeAtoms[JSTYPE_VOID] &&
3312 (pc += js_CodeSpec[op].length) < endpc) {
3313 op = (JSOp) *pc;
3314 return op == JSOP_EQ || op == JSOP_NE ||
3315 op == JSOP_STRICTEQ || op == JSOP_STRICTNE;
3316 }
3317 return JS_FALSE;
3318
3319 default:
3320 /*
3321 * At this point, anything but an extended atom index prefix means
3322 * we're not detecting.
3323</