Parent Directory
|
Revision Log
Add SpiderMonkey from Firefox 3.1b1. The following directories and files were removed: correct/, correct.js liveconnect/ nanojit/ t/ v8/ vprof/ xpconnect/ all JavaScript files (Y.js, call.js, if.js, math-partial-sums.js, md5.js, perfect.js, trace-test.js, trace.js)
1 | siliconforks | 332 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
2 | * vim: set ts=8 sw=4 et tw=79: | ||
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 | * JavaScript bytecode interpreter. | ||
43 | */ | ||
44 | #include "jsstddef.h" | ||
45 | #include <stdio.h> | ||
46 | #include <string.h> | ||
47 | #include <math.h> | ||
48 | #include "jstypes.h" | ||
49 | #include "jsarena.h" /* Added by JSIFY */ | ||
50 | #include "jsutil.h" /* Added by JSIFY */ | ||
51 | #include "jsprf.h" | ||
52 | #include "jsapi.h" | ||
53 | #include "jsarray.h" | ||
54 | #include "jsatom.h" | ||
55 | #include "jsbool.h" | ||
56 | #include "jscntxt.h" | ||
57 | #include "jsversion.h" | ||
58 | #include "jsdbgapi.h" | ||
59 | #include "jsfun.h" | ||
60 | #include "jsgc.h" | ||
61 | #include "jsinterp.h" | ||
62 | #include "jsiter.h" | ||
63 | #include "jslock.h" | ||
64 | #include "jsnum.h" | ||
65 | #include "jsobj.h" | ||
66 | #include "jsopcode.h" | ||
67 | #include "jsscan.h" | ||
68 | #include "jsscope.h" | ||
69 | #include "jsscript.h" | ||
70 | #include "jsstr.h" | ||
71 | #include "jsstaticcheck.h" | ||
72 | #include "jstracer.h" | ||
73 | |||
74 | #ifdef INCLUDE_MOZILLA_DTRACE | ||
75 | #include "jsdtracef.h" | ||
76 | #endif | ||
77 | |||
78 | #if JS_HAS_XML_SUPPORT | ||
79 | #include "jsxml.h" | ||
80 | #endif | ||
81 | |||
82 | #include "jsautooplen.h" | ||
83 | |||
84 | /* jsinvoke_cpp___ indicates inclusion from jsinvoke.cpp. */ | ||
85 | #if !JS_LONE_INTERPRET ^ defined jsinvoke_cpp___ | ||
86 | |||
87 | uint32 | ||
88 | js_GenerateShape(JSContext *cx, JSBool gcLocked) | ||
89 | { | ||
90 | JSRuntime *rt; | ||
91 | uint32 shape; | ||
92 | |||
93 | rt = cx->runtime; | ||
94 | shape = JS_ATOMIC_INCREMENT(&rt->shapeGen); | ||
95 | JS_ASSERT(shape != 0); | ||
96 | if (shape & SHAPE_OVERFLOW_BIT) { | ||
97 | rt->gcPoke = JS_TRUE; | ||
98 | js_GC(cx, gcLocked ? GC_LOCK_HELD : GC_NORMAL); | ||
99 | shape = JS_ATOMIC_INCREMENT(&rt->shapeGen); | ||
100 | JS_ASSERT(shape != 0); | ||
101 | JS_ASSERT_IF(shape & SHAPE_OVERFLOW_BIT, | ||
102 | JS_PROPERTY_CACHE(cx).disabled); | ||
103 | } | ||
104 | return shape; | ||
105 | } | ||
106 | |||
107 | void | ||
108 | js_FillPropertyCache(JSContext *cx, JSObject *obj, jsuword kshape, | ||
109 | uintN scopeIndex, uintN protoIndex, | ||
110 | JSObject *pobj, JSScopeProperty *sprop, | ||
111 | JSPropCacheEntry **entryp) | ||
112 | { | ||
113 | JSPropertyCache *cache; | ||
114 | jsbytecode *pc; | ||
115 | JSScope *scope; | ||
116 | JSOp op; | ||
117 | const JSCodeSpec *cs; | ||
118 | jsuword vword; | ||
119 | ptrdiff_t pcoff; | ||
120 | jsuword khash; | ||
121 | JSAtom *atom; | ||
122 | JSPropCacheEntry *entry; | ||
123 | |||
124 | JS_ASSERT(!cx->runtime->gcRunning); | ||
125 | cache = &JS_PROPERTY_CACHE(cx); | ||
126 | pc = cx->fp->regs->pc; | ||
127 | if (cache->disabled || (cx->fp->flags & JSFRAME_EVAL)) { | ||
128 | PCMETER(cache->disfills++); | ||
129 | *entryp = NULL; | ||
130 | return; | ||
131 | } | ||
132 | |||
133 | /* | ||
134 | * Check for fill from js_SetPropertyHelper where the setter removed sprop | ||
135 | * from pobj's scope (via unwatch or delete, e.g.). | ||
136 | */ | ||
137 | scope = OBJ_SCOPE(pobj); | ||
138 | JS_ASSERT(scope->object == pobj); | ||
139 | if (!SCOPE_HAS_PROPERTY(scope, sprop)) { | ||
140 | PCMETER(cache->oddfills++); | ||
141 | *entryp = NULL; | ||
142 | return; | ||
143 | } | ||
144 | |||
145 | /* | ||
146 | * Check for overdeep scope and prototype chain. Because resolve, getter, | ||
147 | * and setter hooks can change the prototype chain using JS_SetPrototype | ||
148 | * after js_LookupPropertyWithFlags has returned the nominal protoIndex, | ||
149 | * we have to validate protoIndex if it is non-zero. If it is zero, then | ||
150 | * we know thanks to the SCOPE_HAS_PROPERTY test above, and from the fact | ||
151 | * that obj == pobj, that protoIndex is invariant. | ||
152 | * | ||
153 | * The scopeIndex can't be wrong. We require JS_SetParent calls to happen | ||
154 | * before any running script might consult a parent-linked scope chain. If | ||
155 | * this requirement is not satisfied, the fill in progress will never hit, | ||
156 | * but vcap vs. scope shape tests ensure nothing malfunctions. | ||
157 | */ | ||
158 | JS_ASSERT_IF(scopeIndex == 0 && protoIndex == 0, obj == pobj); | ||
159 | if (protoIndex != 0) { | ||
160 | JSObject *tmp; | ||
161 | |||
162 | JS_ASSERT(pobj != obj); | ||
163 | protoIndex = 1; | ||
164 | tmp = obj; | ||
165 | for (;;) { | ||
166 | tmp = OBJ_GET_PROTO(cx, tmp); | ||
167 | if (!tmp) { | ||
168 | PCMETER(cache->noprotos++); | ||
169 | *entryp = NULL; | ||
170 | return; | ||
171 | } | ||
172 | if (tmp == pobj) | ||
173 | break; | ||
174 | ++protoIndex; | ||
175 | } | ||
176 | } | ||
177 | if (scopeIndex > PCVCAP_SCOPEMASK || protoIndex > PCVCAP_PROTOMASK) { | ||
178 | PCMETER(cache->longchains++); | ||
179 | *entryp = NULL; | ||
180 | return; | ||
181 | } | ||
182 | |||
183 | /* | ||
184 | * Optimize the cached vword based on our parameters and the current pc's | ||
185 | * opcode format flags. | ||
186 | */ | ||
187 | op = (JSOp) *pc; | ||
188 | cs = &js_CodeSpec[op]; | ||
189 | |||
190 | do { | ||
191 | /* | ||
192 | * Check for a prototype "plain old method" callee computation. What | ||
193 | * is a plain old method? It's a function-valued property with stub | ||
194 | * getter and setter, so get of a function is idempotent and set is | ||
195 | * transparent. | ||
196 | */ | ||
197 | if (cs->format & JOF_CALLOP) { | ||
198 | if (SPROP_HAS_STUB_GETTER(sprop) && | ||
199 | SPROP_HAS_VALID_SLOT(sprop, scope)) { | ||
200 | jsval v; | ||
201 | |||
202 | v = LOCKED_OBJ_GET_SLOT(pobj, sprop->slot); | ||
203 | if (VALUE_IS_FUNCTION(cx, v)) { | ||
204 | /* | ||
205 | * Great, we have a function-valued prototype property | ||
206 | * where the getter is JS_PropertyStub. The type id in | ||
207 | * pobj's scope does not evolve with changes to property | ||
208 | * values, however. | ||
209 | * | ||
210 | * So here, on first cache fill for this method, we brand | ||
211 | * the scope with a new shape and set the SCOPE_BRANDED | ||
212 | * flag. Once this scope flag is set, any write that adds | ||
213 | * or deletes a function-valued plain old property in | ||
214 | * scope->object will result in shape being regenerated. | ||
215 | */ | ||
216 | if (!SCOPE_IS_BRANDED(scope)) { | ||
217 | PCMETER(cache->brandfills++); | ||
218 | #ifdef DEBUG_notme | ||
219 | fprintf(stderr, | ||
220 | "branding %p (%s) for funobj %p (%s), kshape %lu\n", | ||
221 | pobj, LOCKED_OBJ_GET_CLASS(pobj)->name, | ||
222 | JSVAL_TO_OBJECT(v), | ||
223 | JS_GetFunctionName(GET_FUNCTION_PRIVATE(cx, | ||
224 | JSVAL_TO_OBJECT(v))), | ||
225 | kshape); | ||
226 | #endif | ||
227 | SCOPE_MAKE_UNIQUE_SHAPE(cx, scope); | ||
228 | SCOPE_SET_BRANDED(scope); | ||
229 | kshape = scope->shape; | ||
230 | } | ||
231 | vword = JSVAL_OBJECT_TO_PCVAL(v); | ||
232 | break; | ||
233 | } | ||
234 | } | ||
235 | } | ||
236 | |||
237 | /* If getting a value via a stub getter, we can cache the slot. */ | ||
238 | if (!(cs->format & JOF_SET) && | ||
239 | SPROP_HAS_STUB_GETTER(sprop) && | ||
240 | SPROP_HAS_VALID_SLOT(sprop, scope)) { | ||
241 | /* Great, let's cache sprop's slot and use it on cache hit. */ | ||
242 | vword = SLOT_TO_PCVAL(sprop->slot); | ||
243 | } else { | ||
244 | /* Best we can do is to cache sprop (still a nice speedup). */ | ||
245 | vword = SPROP_TO_PCVAL(sprop); | ||
246 | } | ||
247 | } while (0); | ||
248 | |||
249 | /* | ||
250 | * Our caller preserved the scope shape prior to the js_GetPropertyHelper | ||
251 | * or similar call out of the interpreter. We want to cache under that | ||
252 | * shape if op is overtly mutating, to bias for the case where the mutator | ||
253 | * udpates shape predictably. | ||
254 | * | ||
255 | * Note that an apparently non-mutating op such as JSOP_NAME may still | ||
256 | * mutate the base object via, e.g., lazy standard class initialization, | ||
257 | * but that is a one-time event and we'll have to miss the old shape and | ||
258 | * re-fill under the new one. | ||
259 | */ | ||
260 | if (!(cs->format & (JOF_SET | JOF_INCDEC)) && obj == pobj) | ||
261 | kshape = scope->shape; | ||
262 | |||
263 | khash = PROPERTY_CACHE_HASH_PC(pc, kshape); | ||
264 | if (obj == pobj) { | ||
265 | JS_ASSERT(kshape != 0 || scope->shape != 0); | ||
266 | JS_ASSERT(scopeIndex == 0 && protoIndex == 0); | ||
267 | JS_ASSERT(OBJ_SCOPE(obj)->object == obj); | ||
268 | } else { | ||
269 | if (op == JSOP_LENGTH) { | ||
270 | atom = cx->runtime->atomState.lengthAtom; | ||
271 | } else { | ||
272 | pcoff = (JOF_TYPE(cs->format) == JOF_SLOTATOM) ? 2 : 0; | ||
273 | GET_ATOM_FROM_BYTECODE(cx->fp->script, pc, pcoff, atom); | ||
274 | } | ||
275 | JS_ASSERT_IF(scopeIndex == 0, | ||
276 | protoIndex != 1 || OBJ_GET_PROTO(cx, obj) == pobj); | ||
277 | if (scopeIndex != 0 || protoIndex != 1) { | ||
278 | khash = PROPERTY_CACHE_HASH_ATOM(atom, obj, pobj); | ||
279 | PCMETER(if (PCVCAP_TAG(cache->table[khash].vcap) <= 1) | ||
280 | cache->pcrecycles++); | ||
281 | pc = (jsbytecode *) atom; | ||
282 | kshape = (jsuword) obj; | ||
283 | } | ||
284 | } | ||
285 | |||
286 | entry = &cache->table[khash]; | ||
287 | PCMETER(if (entry != *entryp) cache->modfills++); | ||
288 | PCMETER(if (!PCVAL_IS_NULL(entry->vword)) cache->recycles++); | ||
289 | entry->kpc = pc; | ||
290 | entry->kshape = kshape; | ||
291 | entry->vcap = PCVCAP_MAKE(scope->shape, scopeIndex, protoIndex); | ||
292 | entry->vword = vword; | ||
293 | *entryp = entry; | ||
294 | |||
295 | cache->empty = JS_FALSE; | ||
296 | PCMETER(cache->fills++); | ||
297 | } | ||
298 | |||
299 | JSAtom * | ||
300 | js_FullTestPropertyCache(JSContext *cx, jsbytecode *pc, | ||
301 | JSObject **objp, JSObject **pobjp, | ||
302 | JSPropCacheEntry **entryp) | ||
303 | { | ||
304 | JSOp op; | ||
305 | const JSCodeSpec *cs; | ||
306 | ptrdiff_t pcoff; | ||
307 | JSAtom *atom; | ||
308 | JSObject *obj, *pobj, *tmp; | ||
309 | JSPropCacheEntry *entry; | ||
310 | uint32 vcap; | ||
311 | |||
312 | JS_ASSERT(JS_UPTRDIFF(pc, cx->fp->script->code) < cx->fp->script->length); | ||
313 | |||
314 | op = (JSOp) *pc; | ||
315 | cs = &js_CodeSpec[op]; | ||
316 | if (op == JSOP_LENGTH) { | ||
317 | atom = cx->runtime->atomState.lengthAtom; | ||
318 | } else { | ||
319 | pcoff = (JOF_TYPE(cs->format) == JOF_SLOTATOM) ? 2 : 0; | ||
320 | GET_ATOM_FROM_BYTECODE(cx->fp->script, pc, pcoff, atom); | ||
321 | } | ||
322 | |||
323 | obj = *objp; | ||
324 | JS_ASSERT(OBJ_IS_NATIVE(obj)); | ||
325 | entry = &JS_PROPERTY_CACHE(cx).table[PROPERTY_CACHE_HASH_ATOM(atom, obj, NULL)]; | ||
326 | *entryp = entry; | ||
327 | vcap = entry->vcap; | ||
328 | |||
329 | if (entry->kpc != (jsbytecode *) atom) { | ||
330 | PCMETER(JS_PROPERTY_CACHE(cx).idmisses++); | ||
331 | |||
332 | #ifdef DEBUG_notme | ||
333 | entry = &JS_PROPERTY_CACHE(cx).table[PROPERTY_CACHE_HASH_PC(pc, OBJ_SHAPE(obj))]; | ||
334 | fprintf(stderr, | ||
335 | "id miss for %s from %s:%u" | ||
336 | " (pc %u, kpc %u, kshape %u, shape %u)\n", | ||
337 | js_AtomToPrintableString(cx, atom), | ||
338 | cx->fp->script->filename, | ||
339 | js_PCToLineNumber(cx, cx->fp->script, pc), | ||
340 | pc - cx->fp->script->code, | ||
341 | entry->kpc - cx->fp->script->code, | ||
342 | entry->kshape, | ||
343 | OBJ_SHAPE(obj)); | ||
344 | js_Disassemble1(cx, cx->fp->script, pc, | ||
345 | PTRDIFF(pc, cx->fp->script->code, jsbytecode), | ||
346 | JS_FALSE, stderr); | ||
347 | #endif | ||
348 | |||
349 | return atom; | ||
350 | } | ||
351 | |||
352 | if (entry->kshape != (jsuword) obj) { | ||
353 | PCMETER(JS_PROPERTY_CACHE(cx).komisses++); | ||
354 | return atom; | ||
355 | } | ||
356 | |||
357 | pobj = obj; | ||
358 | JS_LOCK_OBJ(cx, pobj); | ||
359 | |||
360 | if (JOF_MODE(cs->format) == JOF_NAME) { | ||
361 | while (vcap & (PCVCAP_SCOPEMASK << PCVCAP_PROTOBITS)) { | ||
362 | tmp = LOCKED_OBJ_GET_PARENT(pobj); | ||
363 | if (!tmp || !OBJ_IS_NATIVE(tmp)) | ||
364 | break; | ||
365 | JS_UNLOCK_OBJ(cx, pobj); | ||
366 | pobj = tmp; | ||
367 | JS_LOCK_OBJ(cx, pobj); | ||
368 | vcap -= PCVCAP_PROTOSIZE; | ||
369 | } | ||
370 | |||
371 | *objp = pobj; | ||
372 | } | ||
373 | |||
374 | while (vcap & PCVCAP_PROTOMASK) { | ||
375 | tmp = LOCKED_OBJ_GET_PROTO(pobj); | ||
376 | if (!tmp || !OBJ_IS_NATIVE(tmp)) | ||
377 | break; | ||
378 | JS_UNLOCK_OBJ(cx, pobj); | ||
379 | pobj = tmp; | ||
380 | JS_LOCK_OBJ(cx, pobj); | ||
381 | --vcap; | ||
382 | } | ||
383 | |||
384 | if (PCVCAP_SHAPE(vcap) == OBJ_SHAPE(pobj)) { | ||
385 | #ifdef DEBUG | ||
386 | jsid id = ATOM_TO_JSID(atom); | ||
387 | |||
388 | CHECK_FOR_STRING_INDEX(id); | ||
389 | JS_ASSERT(SCOPE_GET_PROPERTY(OBJ_SCOPE(pobj), id)); | ||
390 | JS_ASSERT(OBJ_SCOPE(pobj)->object == pobj); | ||
391 | #endif | ||
392 | *pobjp = pobj; | ||
393 | return NULL; | ||
394 | } | ||
395 | |||
396 | PCMETER(JS_PROPERTY_CACHE(cx).vcmisses++); | ||
397 | JS_UNLOCK_OBJ(cx, pobj); | ||
398 | return atom; | ||
399 | } | ||
400 | |||
401 | #ifdef DEBUG | ||
402 | #define ASSERT_CACHE_IS_EMPTY(cache) \ | ||
403 | JS_BEGIN_MACRO \ | ||
404 | JSPropertyCache *cache_ = (cache); \ | ||
405 | uintN i_; \ | ||
406 | JS_ASSERT(cache_->empty); \ | ||
407 | for (i_ = 0; i_ < PROPERTY_CACHE_SIZE; i_++) { \ | ||
408 | JS_ASSERT(!cache_->table[i_].kpc); \ | ||
409 | JS_ASSERT(!cache_->table[i_].kshape); \ | ||
410 | JS_ASSERT(!cache_->table[i_].vcap); \ | ||
411 | JS_ASSERT(!cache_->table[i_].vword); \ | ||
412 | } \ | ||
413 | JS_END_MACRO | ||
414 | #else | ||
415 | #define ASSERT_CACHE_IS_EMPTY(cache) ((void)0) | ||
416 | #endif | ||
417 | |||
418 | JS_STATIC_ASSERT(PCVAL_NULL == 0); | ||
419 | |||
420 | void | ||
421 | js_FlushPropertyCache(JSContext *cx) | ||
422 | { | ||
423 | JSPropertyCache *cache; | ||
424 | |||
425 | cache = &JS_PROPERTY_CACHE(cx); | ||
426 | if (cache->empty) { | ||
427 | ASSERT_CACHE_IS_EMPTY(cache); | ||
428 | return; | ||
429 | } | ||
430 | |||
431 | memset(cache->table, 0, sizeof cache->table); | ||
432 | cache->empty = JS_TRUE; | ||
433 | |||
434 | #ifdef JS_PROPERTY_CACHE_METERING | ||
435 | { static FILE *fp; | ||
436 | if (!fp) | ||
437 | fp = fopen("/tmp/propcache.stats", "w"); | ||
438 | if (fp) { | ||
439 | fputs("Property cache stats for ", fp); | ||
440 | #ifdef JS_THREADSAFE | ||
441 | fprintf(fp, "thread %lu, ", (unsigned long) cx->thread->id); | ||
442 | #endif | ||
443 | fprintf(fp, "GC %u\n", cx->runtime->gcNumber); | ||
444 | |||
445 | # define P(mem) fprintf(fp, "%11s %10lu\n", #mem, (unsigned long)cache->mem) | ||
446 | P(fills); | ||
447 | P(nofills); | ||
448 | P(rofills); | ||
449 | P(disfills); | ||
450 | P(oddfills); | ||
451 | P(modfills); | ||
452 | P(brandfills); | ||
453 | P(noprotos); | ||
454 | P(longchains); | ||
455 | P(recycles); | ||
456 | P(pcrecycles); | ||
457 | P(tests); | ||
458 | P(pchits); | ||
459 | P(protopchits); | ||
460 | P(initests); | ||
461 | P(inipchits); | ||
462 | P(inipcmisses); | ||
463 | P(settests); | ||
464 | P(addpchits); | ||
465 | P(setpchits); | ||
466 | P(setpcmisses); | ||
467 | P(slotchanges); | ||
468 | P(setmisses); | ||
469 | P(idmisses); | ||
470 | P(komisses); | ||
471 | P(vcmisses); | ||
472 | P(misses); | ||
473 | P(flushes); | ||
474 | P(pcpurges); | ||
475 | # undef P | ||
476 | |||
477 | fprintf(fp, "hit rates: pc %g%% (proto %g%%), set %g%%, ini %g%%, full %g%%\n", | ||
478 | (100. * cache->pchits) / cache->tests, | ||
479 | (100. * cache->protopchits) / cache->tests, | ||
480 | (100. * (cache->addpchits + cache->setpchits)) | ||
481 | / cache->settests, | ||
482 | (100. * cache->inipchits) / cache->initests, | ||
483 | (100. * (cache->tests - cache->misses)) / cache->tests); | ||
484 | fflush(fp); | ||
485 | } | ||
486 | } | ||
487 | #endif | ||
488 | |||
489 | PCMETER(cache->flushes++); | ||
490 | } | ||
491 | |||
492 | void | ||
493 | js_FlushPropertyCacheForScript(JSContext *cx, JSScript *script) | ||
494 | { | ||
495 | JSPropertyCache *cache; | ||
496 | JSPropCacheEntry *entry; | ||
497 | |||
498 | cache = &JS_PROPERTY_CACHE(cx); | ||
499 | for (entry = cache->table; entry < cache->table + PROPERTY_CACHE_SIZE; | ||
500 | entry++) { | ||
501 | if (JS_UPTRDIFF(entry->kpc, script->code) < script->length) { | ||
502 | entry->kpc = NULL; | ||
503 | entry->kshape = 0; | ||
504 | #ifdef DEBUG | ||
505 | entry->vcap = entry->vword = 0; | ||
506 | #endif | ||
507 | } | ||
508 | } | ||
509 | } | ||
510 | |||
511 | void | ||
512 | js_DisablePropertyCache(JSContext *cx) | ||
513 | { | ||
514 | JS_ASSERT(JS_PROPERTY_CACHE(cx).disabled >= 0); | ||
515 | ++JS_PROPERTY_CACHE(cx).disabled; | ||
516 | } | ||
517 | |||
518 | void | ||
519 | js_EnablePropertyCache(JSContext *cx) | ||
520 | { | ||
521 | --JS_PROPERTY_CACHE(cx).disabled; | ||
522 | JS_ASSERT(JS_PROPERTY_CACHE(cx).disabled >= 0); | ||
523 | } | ||
524 | |||
525 | /* | ||
526 | * Check if the current arena has enough space to fit nslots after sp and, if | ||
527 | * so, reserve the necessary space. | ||
528 | */ | ||
529 | static JSBool | ||
530 | AllocateAfterSP(JSContext *cx, jsval *sp, uintN nslots) | ||
531 | { | ||
532 | uintN surplus; | ||
533 | jsval *sp2; | ||
534 | |||
535 | JS_ASSERT((jsval *) cx->stackPool.current->base <= sp); | ||
536 | JS_ASSERT(sp <= (jsval *) cx->stackPool.current->avail); | ||
537 | surplus = (jsval *) cx->stackPool.current->avail - sp; | ||
538 | if (nslots <= surplus) | ||
539 | return JS_TRUE; | ||
540 | |||
541 | /* | ||
542 | * No room before current->avail, check if the arena has enough space to | ||
543 | * fit the missing slots before the limit. | ||
544 | */ | ||
545 | if (nslots > (size_t) ((jsval *) cx->stackPool.current->limit - sp)) | ||
546 | return JS_FALSE; | ||
547 | |||
548 | JS_ARENA_ALLOCATE_CAST(sp2, jsval *, &cx->stackPool, | ||
549 | (nslots - surplus) * sizeof(jsval)); | ||
550 | JS_ASSERT(sp2 == sp + surplus); | ||
551 | return JS_TRUE; | ||
552 | } | ||
553 | |||
554 | JS_STATIC_INTERPRET jsval * | ||
555 | js_AllocRawStack(JSContext *cx, uintN nslots, void **markp) | ||
556 | { | ||
557 | jsval *sp; | ||
558 | |||
559 | if (!cx->stackPool.first.next) { | ||
560 | int64 *timestamp; | ||
561 | |||
562 | JS_ARENA_ALLOCATE_CAST(timestamp, int64 *, | ||
563 | &cx->stackPool, sizeof *timestamp); | ||
564 | if (!timestamp) { | ||
565 | js_ReportOutOfScriptQuota(cx); | ||
566 | return NULL; | ||
567 | } | ||
568 | *timestamp = JS_Now(); | ||
569 | } | ||
570 | |||
571 | if (markp) | ||
572 | *markp = JS_ARENA_MARK(&cx->stackPool); | ||
573 | JS_ARENA_ALLOCATE_CAST(sp, jsval *, &cx->stackPool, nslots * sizeof(jsval)); | ||
574 | if (!sp) | ||
575 | js_ReportOutOfScriptQuota(cx); | ||
576 | return sp; | ||
577 | } | ||
578 | |||
579 | JS_STATIC_INTERPRET void | ||
580 | js_FreeRawStack(JSContext *cx, void *mark) | ||
581 | { | ||
582 | JS_ARENA_RELEASE(&cx->stackPool, mark); | ||
583 | } | ||
584 | |||
585 | JS_FRIEND_API(jsval *) | ||
586 | js_AllocStack(JSContext *cx, uintN nslots, void **markp) | ||
587 | { | ||
588 | jsval *sp; | ||
589 | JSArena *a; | ||
590 | JSStackHeader *sh; | ||
591 | |||
592 | /* Callers don't check for zero nslots: we do to avoid empty segments. */ | ||
593 | if (nslots == 0) { | ||
594 | *markp = NULL; | ||
595 | return (jsval *) JS_ARENA_MARK(&cx->stackPool); | ||
596 | } | ||
597 | |||
598 | /* Allocate 2 extra slots for the stack segment header we'll likely need. */ | ||
599 | sp = js_AllocRawStack(cx, 2 + nslots, markp); | ||
600 | if (!sp) | ||
601 | return NULL; | ||
602 | |||
603 | /* Try to avoid another header if we can piggyback on the last segment. */ | ||
604 | a = cx->stackPool.current; | ||
605 | sh = cx->stackHeaders; | ||
606 | if (sh && JS_STACK_SEGMENT(sh) + sh->nslots == sp) { | ||
607 | /* Extend the last stack segment, give back the 2 header slots. */ | ||
608 | sh->nslots += nslots; | ||
609 | a->avail -= 2 * sizeof(jsval); | ||
610 | } else { | ||
611 | /* | ||
612 | * Need a new stack segment, so allocate and push a stack segment | ||
613 | * header from the 2 extra slots. | ||
614 | */ | ||
615 | sh = (JSStackHeader *)sp; | ||
616 | sh->nslots = nslots; | ||
617 | sh->down = cx->stackHeaders; | ||
618 | cx->stackHeaders = sh; | ||
619 | sp += 2; | ||
620 | } | ||
621 | |||
622 | /* | ||
623 | * Store JSVAL_NULL using memset, to let compilers optimize as they see | ||
624 | * fit, in case a caller allocates and pushes GC-things one by one, which | ||
625 | * could nest a last-ditch GC that will scan this segment. | ||
626 | */ | ||
627 | memset(sp, 0, nslots * sizeof(jsval)); | ||
628 | return sp; | ||
629 | } | ||
630 | |||
631 | JS_FRIEND_API(void) | ||
632 | js_FreeStack(JSContext *cx, void *mark) | ||
633 | { | ||
634 | JSStackHeader *sh; | ||
635 | jsuword slotdiff; | ||
636 | |||
637 | /* Check for zero nslots allocation special case. */ | ||
638 | if (!mark) | ||
639 | return; | ||
640 | |||
641 | /* We can assert because js_FreeStack always balances js_AllocStack. */ | ||
642 | sh = cx->stackHeaders; | ||
643 | JS_ASSERT(sh); | ||
644 | |||
645 | /* If mark is in the current segment, reduce sh->nslots, else pop sh. */ | ||
646 | slotdiff = JS_UPTRDIFF(mark, JS_STACK_SEGMENT(sh)) / sizeof(jsval); | ||
647 | if (slotdiff < (jsuword)sh->nslots) | ||
648 | sh->nslots = slotdiff; | ||
649 | else | ||
650 | cx->stackHeaders = sh->down; | ||
651 | |||
652 | /* Release the stackPool space allocated since mark was set. */ | ||
653 | JS_ARENA_RELEASE(&cx->stackPool, mark); | ||
654 | } | ||
655 | |||
656 | JSObject * | ||
657 | js_GetScopeChain(JSContext *cx, JSStackFrame *fp) | ||
658 | { | ||
659 | JSObject *obj, *cursor, *clonedChild, *parent; | ||
660 | JSTempValueRooter tvr; | ||
661 | |||
662 | obj = fp->blockChain; | ||
663 | if (!obj) { | ||
664 | /* | ||
665 | * Don't force a call object for a lightweight function call, but do | ||
666 | * insist that there is a call object for a heavyweight function call. | ||
667 | */ | ||
668 | JS_ASSERT(!fp->fun || | ||
669 | !(fp->fun->flags & JSFUN_HEAVYWEIGHT) || | ||
670 | fp->callobj); | ||
671 | JS_ASSERT(fp->scopeChain); | ||
672 | return fp->scopeChain; | ||
673 | } | ||
674 | |||
675 | /* | ||
676 | * We have one or more lexical scopes to reflect into fp->scopeChain, so | ||
677 | * make sure there's a call object at the current head of the scope chain, | ||
678 | * if this frame is a call frame. | ||
679 | */ | ||
680 | if (fp->fun && !fp->callobj) { | ||
681 | JS_ASSERT(OBJ_GET_CLASS(cx, fp->scopeChain) != &js_BlockClass || | ||
682 | OBJ_GET_PRIVATE(cx, fp->scopeChain) != fp); | ||
683 | if (!js_GetCallObject(cx, fp, fp->scopeChain)) | ||
684 | return NULL; | ||
685 | } | ||
686 | |||
687 | /* | ||
688 | * Clone the block chain. To avoid recursive cloning we set the parent of | ||
689 | * the cloned child after we clone the parent. In the following loop when | ||
690 | * clonedChild is null it indicates the first iteration when no special GC | ||
691 | * rooting is necessary. On the second and the following iterations we | ||
692 | * have to protect cloned so far chain against the GC during cloning of | ||
693 | * the cursor object. | ||
694 | */ | ||
695 | cursor = obj; | ||
696 | clonedChild = NULL; | ||
697 | for (;;) { | ||
698 | parent = OBJ_GET_PARENT(cx, cursor); | ||
699 | |||
700 | /* | ||
701 | * We pass fp->scopeChain and not null even if we override the parent | ||
702 | * slot later as null triggers useless calculations of slot's value in | ||
703 | * js_NewObject that js_CloneBlockObject calls. | ||
704 | */ | ||
705 | cursor = js_CloneBlockObject(cx, cursor, fp->scopeChain, fp); | ||
706 | if (!cursor) { | ||
707 | if (clonedChild) | ||
708 | JS_POP_TEMP_ROOT(cx, &tvr); | ||
709 | return NULL; | ||
710 | } | ||
711 | if (!clonedChild) { | ||
712 | /* | ||
713 | * The first iteration. Check if other follow and root obj if so | ||
714 | * to protect the whole cloned chain against GC. | ||
715 | */ | ||
716 | obj = cursor; | ||
717 | if (!parent) | ||
718 | break; | ||
719 | JS_PUSH_TEMP_ROOT_OBJECT(cx, obj, &tvr); | ||
720 | } else { | ||
721 | /* | ||
722 | * Avoid OBJ_SET_PARENT overhead as clonedChild cannot escape to | ||
723 | * other threads. | ||
724 | */ | ||
725 | STOBJ_SET_PARENT(clonedChild, cursor); | ||
726 | if (!parent) { | ||
727 | JS_ASSERT(tvr.u.value == OBJECT_TO_JSVAL(obj)); | ||
728 | JS_POP_TEMP_ROOT(cx, &tvr); | ||
729 | break; | ||
730 | } | ||
731 | } | ||
732 | clonedChild = cursor; | ||
733 | cursor = parent; | ||
734 | } | ||
735 | fp->flags |= JSFRAME_POP_BLOCKS; | ||
736 | fp->scopeChain = obj; | ||
737 | fp->blockChain = NULL; | ||
738 | return obj; | ||
739 | } | ||
740 | |||
741 | JSBool | ||
742 | js_GetPrimitiveThis(JSContext *cx, jsval *vp, JSClass *clasp, jsval *thisvp) | ||
743 | { | ||
744 | jsval v; | ||
745 | JSObject *obj; | ||
746 | |||
747 | v = vp[1]; | ||
748 | if (JSVAL_IS_OBJECT(v)) { | ||
749 | obj = JS_THIS_OBJECT(cx, vp); | ||
750 | if (!JS_InstanceOf(cx, obj, clasp, vp + 2)) | ||
751 | return JS_FALSE; | ||
752 | v = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); | ||
753 | } | ||
754 | *thisvp = v; | ||
755 | return JS_TRUE; | ||
756 | } | ||
757 | |||
758 | /* | ||
759 | * ECMA requires "the global object", but in embeddings such as the browser, | ||
760 | * which have multiple top-level objects (windows, frames, etc. in the DOM), | ||
761 | * we prefer fun's parent. An example that causes this code to run: | ||
762 | * | ||
763 | * // in window w1 | ||
764 | * function f() { return this } | ||
765 | * function g() { return f } | ||
766 | * | ||
767 | * // in window w2 | ||
768 | * var h = w1.g() | ||
769 | * alert(h() == w1) | ||
770 | * | ||
771 | * The alert should display "true". | ||
772 | */ | ||
773 | JS_STATIC_INTERPRET JSObject * | ||
774 | js_ComputeGlobalThis(JSContext *cx, JSBool lazy, jsval *argv) | ||
775 | { | ||
776 | JSObject *thisp; | ||
777 | |||
778 | if (JSVAL_IS_PRIMITIVE(argv[-2]) || | ||
779 | !OBJ_GET_PARENT(cx, JSVAL_TO_OBJECT(argv[-2]))) { | ||
780 | thisp = cx->globalObject; | ||
781 | } else { | ||
782 | JSStackFrame *fp; | ||
783 | jsid id; | ||
784 | jsval v; | ||
785 | uintN attrs; | ||
786 | JSBool ok; | ||
787 | JSObject *parent; | ||
788 | |||
789 | /* | ||
790 | * Walk up the parent chain, first checking that the running script | ||
791 | * has access to the callee's parent object. Note that if lazy, the | ||
792 | * running script whose principals we want to check is the script | ||
793 | * associated with fp->down, not with fp. | ||
794 | * | ||
795 | * FIXME: 417851 -- this access check should not be required, as it | ||
796 | * imposes a performance penalty on all js_ComputeGlobalThis calls, | ||
797 | * and it represents a maintenance hazard. | ||
798 | */ | ||
799 | fp = cx->fp; /* quell GCC overwarning */ | ||
800 | if (lazy) { | ||
801 | JS_ASSERT(fp->argv == argv); | ||
802 | fp->dormantNext = cx->dormantFrameChain; | ||
803 | cx->dormantFrameChain = fp; | ||
804 | cx->fp = fp->down; | ||
805 | fp->down = NULL; | ||
806 | } | ||
807 | thisp = JSVAL_TO_OBJECT(argv[-2]); | ||
808 | id = ATOM_TO_JSID(cx->runtime->atomState.parentAtom); | ||
809 | |||
810 | ok = OBJ_CHECK_ACCESS(cx, thisp, id, JSACC_PARENT, &v, &attrs); | ||
811 | if (lazy) { | ||
812 | cx->dormantFrameChain = fp->dormantNext; | ||
813 | fp->dormantNext = NULL; | ||
814 | fp->down = cx->fp; | ||
815 | cx->fp = fp; | ||
816 | } | ||
817 | if (!ok) | ||
818 | return NULL; | ||
819 | |||
820 | thisp = JSVAL_IS_VOID(v) | ||
821 | ? OBJ_GET_PARENT(cx, thisp) | ||
822 | : JSVAL_TO_OBJECT(v); | ||
823 | while ((parent = OBJ_GET_PARENT(cx, thisp)) != NULL) | ||
824 | thisp = parent; | ||
825 | } | ||
826 | |||
827 | OBJ_TO_OUTER_OBJECT(cx, thisp); | ||
828 | if (!thisp) | ||
829 | return NULL; | ||
830 | argv[-1] = OBJECT_TO_JSVAL(thisp); | ||
831 | return thisp; | ||
832 | } | ||
833 | |||
834 | static JSObject * | ||
835 | ComputeThis(JSContext *cx, JSBool lazy, jsval *argv) | ||
836 | { | ||
837 | JSObject *thisp; | ||
838 | |||
839 | JS_ASSERT(!JSVAL_IS_NULL(argv[-1])); | ||
840 | if (!JSVAL_IS_OBJECT(argv[-1])) { | ||
841 | if (!js_PrimitiveToObject(cx, &argv[-1])) | ||
842 | return NULL; | ||
843 | thisp = JSVAL_TO_OBJECT(argv[-1]); | ||
844 | } else { | ||
845 | thisp = JSVAL_TO_OBJECT(argv[-1]); | ||
846 | if (OBJ_GET_CLASS(cx, thisp) == &js_CallClass) | ||
847 | return js_ComputeGlobalThis(cx, lazy, argv); | ||
848 | |||
849 | if (thisp->map->ops->thisObject) { | ||
850 | /* Some objects (e.g., With) delegate 'this' to another object. */ | ||
851 | thisp = thisp->map->ops->thisObject(cx, thisp); | ||
852 | if (!thisp) | ||
853 | return NULL; | ||
854 | } | ||
855 | OBJ_TO_OUTER_OBJECT(cx, thisp); | ||
856 | if (!thisp) | ||
857 | return NULL; | ||
858 | argv[-1] = OBJECT_TO_JSVAL(thisp); | ||
859 | } | ||
860 | return thisp; | ||
861 | } | ||
862 | |||
863 | JSObject * | ||
864 | js_ComputeThis(JSContext *cx, JSBool lazy, jsval *argv) | ||
865 | { | ||
866 | if (JSVAL_IS_NULL(argv[-1])) | ||
867 | return js_ComputeGlobalThis(cx, lazy, argv); | ||
868 | return ComputeThis(cx, lazy, argv); | ||
869 | } | ||
870 | |||
871 | #if JS_HAS_NO_SUCH_METHOD | ||
872 | |||
873 | #define JSSLOT_FOUND_FUNCTION JSSLOT_PRIVATE | ||
874 | #define JSSLOT_SAVED_ID (JSSLOT_PRIVATE + 1) | ||
875 | |||
876 | JSClass js_NoSuchMethodClass = { | ||
877 | "NoSuchMethod", | ||
878 | JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_IS_ANONYMOUS | | ||
879 | JSCLASS_HAS_CACHED_PROTO(JSProto_NoSuchMethod), | ||
880 | JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, | ||
881 | JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, | ||
882 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL | ||
883 | }; | ||
884 | |||
885 | JS_BEGIN_EXTERN_C | ||
886 | |||
887 | JSObject* | ||
888 | js_InitNoSuchMethodClass(JSContext *cx, JSObject* obj); | ||
889 | |||
890 | JS_END_EXTERN_C | ||
891 | |||
892 | JSObject* | ||
893 | js_InitNoSuchMethodClass(JSContext *cx, JSObject* obj) | ||
894 | { | ||
895 | JSObject *proto; | ||
896 | |||
897 | proto = JS_InitClass(cx, obj, NULL, &js_NoSuchMethodClass, NULL, 0, NULL, | ||
898 | NULL, NULL, NULL); | ||
899 | if (!proto) | ||
900 | return NULL; | ||
901 | |||
902 | OBJ_CLEAR_PROTO(cx, proto); | ||
903 | return proto; | ||
904 | } | ||
905 | |||
906 | /* | ||
907 | * When JSOP_CALLPROP or JSOP_CALLELEM does not find the method property of | ||
908 | * the base object, we search for the __noSuchMethod__ method in the base. | ||
909 | * If it exists, we store the method and the property's id into an object of | ||
910 | * NoSuchMethod class and store this object into the callee's stack slot. | ||
911 | * Later, js_Invoke will recognise such an object and transfer control to | ||
912 | * NoSuchMethod that invokes the method like: | ||
913 | * | ||
914 | * this.__noSuchMethod__(id, args) | ||
915 | * | ||
916 | * where id is the name of the method that this invocation attempted to | ||
917 | * call by name, and args is an Array containing this invocation's actual | ||
918 | * parameters. | ||
919 | */ | ||
920 | JS_STATIC_INTERPRET JSBool | ||
921 | js_OnUnknownMethod(JSContext *cx, jsval *vp) | ||
922 | { | ||
923 | JSObject *obj; | ||
924 | jsid id; | ||
925 | JSTempValueRooter tvr; | ||
926 | JSBool ok; | ||
927 | |||
928 | JS_ASSERT(!JSVAL_IS_PRIMITIVE(vp[1])); | ||
929 | obj = JSVAL_TO_OBJECT(vp[1]); | ||
930 | JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr); | ||
931 | |||
932 | MUST_FLOW_THROUGH("out"); | ||
933 | id = ATOM_TO_JSID(cx->runtime->atomState.noSuchMethodAtom); | ||
934 | #if JS_HAS_XML_SUPPORT | ||
935 | if (OBJECT_IS_XML(cx, obj)) { | ||
936 | JSXMLObjectOps *ops; | ||
937 | |||
938 | ops = (JSXMLObjectOps *) obj->map->ops; | ||
939 | obj = ops->getMethod(cx, obj, id, &tvr.u.value); | ||
940 | if (!obj) { | ||
941 | ok = JS_FALSE; | ||
942 | goto out; | ||
943 | } | ||
944 | vp[1] = OBJECT_TO_JSVAL(obj); | ||
945 | } else | ||
946 | #endif | ||
947 | { | ||
948 | ok = OBJ_GET_PROPERTY(cx, obj, id, &tvr.u.value); | ||
949 | if (!ok) | ||
950 | goto out; | ||
951 | } | ||
952 | if (JSVAL_IS_PRIMITIVE(tvr.u.value)) { | ||
953 | vp[0] = tvr.u.value; | ||
954 | } else { | ||
955 | #if JS_HAS_XML_SUPPORT | ||
956 | /* Extract the function name from function::name qname. */ | ||
957 | if (!JSVAL_IS_PRIMITIVE(vp[0])) { | ||
958 | obj = JSVAL_TO_OBJECT(vp[0]); | ||
959 | ok = js_IsFunctionQName(cx, obj, &id); | ||
960 | if (!ok) | ||
961 | goto out; | ||
962 | if (id != 0) | ||
963 | vp[0] = ID_TO_VALUE(id); | ||
964 | } | ||
965 | #endif | ||
966 | obj = js_NewObject(cx, &js_NoSuchMethodClass, NULL, NULL, 0); | ||
967 | if (!obj) { | ||
968 | ok = JS_FALSE; | ||
969 | goto out; | ||
970 | } | ||
971 | obj->fslots[JSSLOT_FOUND_FUNCTION] = tvr.u.value; | ||
972 | obj->fslots[JSSLOT_SAVED_ID] = vp[0]; | ||
973 | vp[0] = OBJECT_TO_JSVAL(obj); | ||
974 | } | ||
975 | ok = JS_TRUE; | ||
976 | |||
977 | out: | ||
978 | JS_POP_TEMP_ROOT(cx, &tvr); | ||
979 | return ok; | ||
980 | } | ||
981 | |||
982 | static JSBool | ||
983 | NoSuchMethod(JSContext *cx, uintN argc, jsval *vp, uint32 flags) | ||
984 | { | ||
985 | jsval *invokevp; | ||
986 | void *mark; | ||
987 | JSBool ok; | ||
988 | JSObject *obj, *argsobj; | ||
989 | |||
990 | invokevp = js_AllocStack(cx, 2 + 2, &mark); | ||
991 | if (!invokevp) | ||
992 | return JS_FALSE; | ||
993 | |||
994 | JS_ASSERT(!JSVAL_IS_PRIMITIVE(vp[0])); | ||
995 | JS_ASSERT(!JSVAL_IS_PRIMITIVE(vp[1])); | ||
996 | obj = JSVAL_TO_OBJECT(vp[0]); | ||
997 | JS_ASSERT(STOBJ_GET_CLASS(obj) == &js_NoSuchMethodClass); | ||
998 | |||
999 | invokevp[0] = obj->fslots[JSSLOT_FOUND_FUNCTION]; | ||
1000 | invokevp[1] = vp[1]; | ||
1001 | invokevp[2] = obj->fslots[JSSLOT_SAVED_ID]; | ||
1002 | argsobj = js_NewArrayObject(cx, argc, vp + 2); | ||
1003 | if (!argsobj) { | ||
1004 | ok = JS_FALSE; | ||
1005 | } else { | ||
1006 | invokevp[3] = OBJECT_TO_JSVAL(argsobj); | ||
1007 | ok = (flags & JSINVOKE_CONSTRUCT) | ||
1008 | ? js_InvokeConstructor(cx, 2, JS_TRUE, invokevp) | ||
1009 | : js_Invoke(cx, 2, invokevp, flags); | ||
1010 | vp[0] = invokevp[0]; | ||
1011 | } | ||
1012 | js_FreeStack(cx, mark); | ||
1013 | return ok; | ||
1014 | } | ||
1015 | |||
1016 | #endif /* JS_HAS_NO_SUCH_METHOD */ | ||
1017 | |||
1018 | /* | ||
1019 | * We check if the function accepts a primitive value as |this|. For that we | ||
1020 | * use a table that maps value's tag into the corresponding function flag. | ||
1021 | */ | ||
1022 | JS_STATIC_ASSERT(JSVAL_INT == 1); | ||
1023 | JS_STATIC_ASSERT(JSVAL_DOUBLE == 2); | ||
1024 | JS_STATIC_ASSERT(JSVAL_STRING == 4); | ||
1025 | JS_STATIC_ASSERT(JSVAL_BOOLEAN == 6); | ||
1026 | |||
1027 | const uint16 js_PrimitiveTestFlags[] = { | ||
1028 | JSFUN_THISP_NUMBER, /* INT */ | ||
1029 | JSFUN_THISP_NUMBER, /* DOUBLE */ | ||
1030 | JSFUN_THISP_NUMBER, /* INT */ | ||
1031 | JSFUN_THISP_STRING, /* STRING */ | ||
1032 | JSFUN_THISP_NUMBER, /* INT */ | ||
1033 | JSFUN_THISP_BOOLEAN, /* BOOLEAN */ | ||
1034 | JSFUN_THISP_NUMBER /* INT */ | ||
1035 | }; | ||
1036 | |||
1037 | /* | ||
1038 | * Find a function reference and its 'this' object implicit first parameter | ||
1039 | * under argc arguments on cx's stack, and call the function. Push missing | ||
1040 | * required arguments, allocate declared local variables, and pop everything | ||
1041 | * when done. Then push the return value. | ||
1042 | */ | ||
1043 | JS_FRIEND_API(JSBool) | ||
1044 | js_Invoke(JSContext *cx, uintN argc, jsval *vp, uintN flags) | ||
1045 | { | ||
1046 | void *mark; | ||
1047 | JSStackFrame frame; | ||
1048 | jsval *sp, *argv, *newvp; | ||
1049 | jsval v; | ||
1050 | JSObject *funobj, *parent; | ||
1051 | JSBool ok; | ||
1052 | JSClass *clasp; | ||
1053 | JSObjectOps *ops; | ||
1054 | JSNative native; | ||
1055 | JSFunction *fun; | ||
1056 | JSScript *script; | ||
1057 | uintN nslots, i; | ||
1058 | uint32 rootedArgsFlag; | ||
1059 | JSInterpreterHook hook; | ||
1060 | void *hookData; | ||
1061 | |||
1062 | /* [vp .. vp + 2 + argc) must belong to the last JS stack arena. */ | ||
1063 | JS_ASSERT((jsval *) cx->stackPool.current->base <= vp); | ||
1064 | JS_ASSERT(vp + 2 + argc <= (jsval *) cx->stackPool.current->avail); | ||
1065 | |||
1066 | /* | ||
1067 | * Mark the top of stack and load frequently-used registers. After this | ||
1068 | * point the control should flow through label out2: to return. | ||
1069 | */ | ||
1070 | mark = JS_ARENA_MARK(&cx->stackPool); | ||
1071 | v = *vp; | ||
1072 | |||
1073 | if (JSVAL_IS_PRIMITIVE(v)) | ||
1074 | goto bad; | ||
1075 | |||
1076 | funobj = JSVAL_TO_OBJECT(v); | ||
1077 | parent = OBJ_GET_PARENT(cx, funobj); | ||
1078 | clasp = OBJ_GET_CLASS(cx, funobj); | ||
1079 | if (clasp != &js_FunctionClass) { | ||
1080 | #if JS_HAS_NO_SUCH_METHOD | ||
1081 | if (clasp == &js_NoSuchMethodClass) { | ||
1082 | ok = NoSuchMethod(cx, argc, vp, flags); | ||
1083 | goto out2; | ||
1084 | } | ||
1085 | #endif | ||
1086 | |||
1087 | /* Function is inlined, all other classes use object ops. */ | ||
1088 | ops = funobj->map->ops; | ||
1089 | |||
1090 | /* | ||
1091 | * XXX this makes no sense -- why convert to function if clasp->call? | ||
1092 | * XXX better to call that hook without converting | ||
1093 | * XXX the only thing that needs fixing is liveconnect | ||
1094 | * | ||
1095 | * Try converting to function, for closure and API compatibility. | ||
1096 | * We attempt the conversion under all circumstances for 1.2, but | ||
1097 | * only if there is a call op defined otherwise. | ||
1098 | */ | ||
1099 | if ((ops == &js_ObjectOps) ? clasp->call : ops->call) { | ||
1100 | ok = clasp->convert(cx, funobj, JSTYPE_FUNCTION, &v); | ||
1101 | if (!ok) | ||
1102 | goto out2; | ||
1103 | |||
1104 | if (VALUE_IS_FUNCTION(cx, v)) { | ||
1105 | /* Make vp refer to funobj to keep it available as argv[-2]. */ | ||
1106 | *vp = v; | ||
1107 | funobj = JSVAL_TO_OBJECT(v); | ||
1108 | parent = OBJ_GET_PARENT(cx, funobj); | ||
1109 | goto have_fun; | ||
1110 | } | ||
1111 | } | ||
1112 | fun = NULL; | ||
1113 | script = NULL; | ||
1114 | nslots = 0; | ||
1115 | |||
1116 | /* Try a call or construct native object op. */ | ||
1117 | if (flags & JSINVOKE_CONSTRUCT) { | ||
1118 | if (!JSVAL_IS_OBJECT(vp[1])) { | ||
1119 | ok = js_PrimitiveToObject(cx, &vp[1]); | ||
1120 | if (!ok) | ||
1121 | goto out2; | ||
1122 | } | ||
1123 | native = ops->construct; | ||
1124 | } else { | ||
1125 | native = ops->call; | ||
1126 | } | ||
1127 | if (!native) | ||
1128 | goto bad; | ||
1129 | } else { | ||
1130 | have_fun: | ||
1131 | /* Get private data and set derived locals from it. */ | ||
1132 | fun = GET_FUNCTION_PRIVATE(cx, funobj); | ||
1133 | nslots = FUN_MINARGS(fun); | ||
1134 | nslots = (nslots > argc) ? nslots - argc : 0; | ||
1135 | if (FUN_INTERPRETED(fun)) { | ||
1136 | native = NULL; | ||
1137 | script = fun->u.i.script; | ||
1138 | } else { | ||
1139 | native = fun->u.n.native; | ||
1140 | script = NULL; | ||
1141 | nslots += fun->u.n.extra; | ||
1142 | } | ||
1143 | |||
1144 | if (JSFUN_BOUND_METHOD_TEST(fun->flags)) { | ||
1145 | /* Handle bound method special case. */ | ||
1146 | vp[1] = OBJECT_TO_JSVAL(parent); | ||
1147 | } else if (!JSVAL_IS_OBJECT(vp[1])) { | ||
1148 | JS_ASSERT(!(flags & JSINVOKE_CONSTRUCT)); | ||
1149 | if (PRIMITIVE_THIS_TEST(fun, vp[1])) | ||
1150 | goto start_call; | ||
1151 | } | ||
1152 | } | ||
1153 | |||
1154 | if (flags & JSINVOKE_CONSTRUCT) { | ||
1155 | JS_ASSERT(!JSVAL_IS_PRIMITIVE(vp[1])); | ||
1156 | } else { | ||
1157 | /* | ||
1158 | * We must call js_ComputeThis in case we are not called from the | ||
1159 | * interpreter, where a prior bytecode has computed an appropriate | ||
1160 | * |this| already. | ||
1161 | * | ||
1162 | * But we need to compute |this| eagerly only for so-called "slow" | ||
1163 | * (i.e., not fast) native functions. Fast natives must use either | ||
1164 | * JS_THIS or JS_THIS_OBJECT, and scripted functions will go through | ||
1165 | * the appropriate this-computing bytecode, e.g., JSOP_THIS. | ||
1166 | */ | ||
1167 | if (native && (!fun || !(fun->flags & JSFUN_FAST_NATIVE))) { | ||
1168 | if (!js_ComputeThis(cx, JS_FALSE, vp + 2)) { | ||
1169 | ok = JS_FALSE; | ||
1170 | goto out2; | ||
1171 | } | ||
1172 | flags |= JSFRAME_COMPUTED_THIS; | ||
1173 | } | ||
1174 | } | ||
1175 | |||
1176 | start_call: | ||
1177 | if (native && fun && (fun->flags & JSFUN_FAST_NATIVE)) { | ||
1178 | #ifdef DEBUG_NOT_THROWING | ||
1179 | JSBool alreadyThrowing = cx->throwing; | ||
1180 | #endif | ||
1181 | JS_ASSERT(nslots == 0); | ||
1182 | #if JS_HAS_LVALUE_RETURN | ||
1183 | /* Set by JS_SetCallReturnValue2, used to return reference types. */ | ||
1184 | cx->rval2set = JS_FALSE; | ||
1185 | #endif | ||
1186 | ok = ((JSFastNative) native)(cx, argc, vp); | ||
1187 | JS_RUNTIME_METER(cx->runtime, nativeCalls); | ||
1188 | #ifdef DEBUG_NOT_THROWING | ||
1189 | if (ok && !alreadyThrowing) | ||
1190 | ASSERT_NOT_THROWING(cx); | ||
1191 | #endif | ||
1192 | goto out2; | ||
1193 | } | ||
1194 | |||
1195 | argv = vp + 2; | ||
1196 | sp = argv + argc; | ||
1197 | |||
1198 | rootedArgsFlag = JSFRAME_ROOTED_ARGV; | ||
1199 | if (nslots != 0) { | ||
1200 | /* | ||
1201 | * The extra slots required by the function continue with argument | ||
1202 | * slots. Thus, when the last stack pool arena does not have room to | ||
1203 | * fit nslots right after sp and AllocateAfterSP fails, we have to copy | ||
1204 | * [vp..vp+2+argc) slots and clear rootedArgsFlag to root the copy. | ||
1205 | */ | ||
1206 | if (!AllocateAfterSP(cx, sp, nslots)) { | ||
1207 | rootedArgsFlag = 0; | ||
1208 | newvp = js_AllocRawStack(cx, 2 + argc + nslots, NULL); | ||
1209 | if (!newvp) { | ||
1210 | ok = JS_FALSE; | ||
1211 | goto out2; | ||
1212 | } | ||
1213 | memcpy(newvp, vp, (2 + argc) * sizeof(jsval)); | ||
1214 | argv = newvp + 2; | ||
1215 | sp = argv + argc; | ||
1216 | } | ||
1217 | |||
1218 | /* Push void to initialize missing args. */ | ||
1219 | i = nslots; | ||
1220 | do { | ||
1221 | *sp++ = JSVAL_VOID; | ||
1222 | } while (--i != 0); | ||
1223 | } | ||
1224 | |||
1225 | /* Allocate space for local variables and stack of interpreted function. */ | ||
1226 | if (script && script->nslots != 0) { | ||
1227 | if (!AllocateAfterSP(cx, sp, script->nslots)) { | ||
1228 | /* NB: Discontinuity between argv and slots, stack slots. */ | ||
1229 | sp = js_AllocRawStack(cx, script->nslots, NULL); | ||
1230 | if (!sp) { | ||
1231 | ok = JS_FALSE; | ||
1232 | goto out2; | ||
1233 | } | ||
1234 | } | ||
1235 | |||
1236 | /* Push void to initialize local variables. */ | ||
1237 | for (jsval *end = sp + fun->u.i.nvars; sp != end; ++sp) | ||
1238 | *sp = JSVAL_VOID; | ||
1239 | } | ||
1240 | |||
1241 | /* | ||
1242 | * Initialize the frame. | ||
1243 | * | ||
1244 | * To set thisp we use an explicit cast and not JSVAL_TO_OBJECT, as vp[1] | ||
1245 | * can be a primitive value here for those native functions specified with | ||
1246 | * JSFUN_THISP_(NUMBER|STRING|BOOLEAN) flags. | ||
1247 | */ | ||
1248 | frame.thisp = (JSObject *)vp[1]; | ||
1249 | frame.varobj = NULL; | ||
1250 | frame.callobj = frame.argsobj = NULL; | ||
1251 | frame.script = script; | ||
1252 | frame.callee = funobj; | ||
1253 | frame.fun = fun; | ||
1254 | frame.argc = argc; | ||
1255 | frame.argv = argv; | ||
1256 | |||
1257 | /* Default return value for a constructor is the new object. */ | ||
1258 | frame.rval = (flags & JSINVOKE_CONSTRUCT) ? vp[1] : JSVAL_VOID; | ||
1259 | frame.down = cx->fp; | ||
1260 | frame.annotation = NULL; | ||
1261 | frame.scopeChain = NULL; /* set below for real, after cx->fp is set */ | ||
1262 | frame.regs = NULL; | ||
1263 | frame.slots = NULL; | ||
1264 | frame.sharpDepth = 0; | ||
1265 | frame.sharpArray = NULL; | ||
1266 | frame.flags = flags | rootedArgsFlag; | ||
1267 | frame.dormantNext = NULL; | ||
1268 | frame.xmlNamespace = NULL; | ||
1269 | frame.blockChain = NULL; | ||
1270 | |||
1271 | MUST_FLOW_THROUGH("out"); | ||
1272 | cx->fp = &frame; | ||
1273 | |||
1274 | /* Init these now in case we goto out before first hook call. */ | ||
1275 | hook = cx->debugHooks->callHook; | ||
1276 | hookData = NULL; | ||
1277 | |||
1278 | /* call the hook if present */ | ||
1279 | if (hook && (native || script)) | ||
1280 | hookData = hook(cx, &frame, JS_TRUE, 0, cx->debugHooks->callHookData); | ||
1281 | |||
1282 | /* Call the function, either a native method or an interpreted script. */ | ||
1283 | if (native) { | ||
1284 | #ifdef DEBUG_NOT_THROWING | ||
1285 | JSBool alreadyThrowing = cx->throwing; | ||
1286 | #endif | ||
1287 | |||
1288 | #if JS_HAS_LVALUE_RETURN | ||
1289 | /* Set by JS_SetCallReturnValue2, used to return reference types. */ | ||
1290 | cx->rval2set = JS_FALSE; | ||
1291 | #endif | ||
1292 | |||
1293 | /* If native, use caller varobj and scopeChain for eval. */ | ||
1294 | JS_ASSERT(!frame.varobj); | ||
1295 | JS_ASSERT(!frame.scopeChain); | ||
1296 | if (frame.down) { | ||
1297 | frame.varobj = frame.down->varobj; | ||
1298 | frame.scopeChain = frame.down->scopeChain; | ||
1299 | } | ||
1300 | |||
1301 | /* But ensure that we have a scope chain. */ | ||
1302 | if (!frame.scopeChain) | ||
1303 | frame.scopeChain = parent; | ||
1304 | |||
1305 | frame.displaySave = NULL; | ||
1306 | ok = native(cx, frame.thisp, argc, frame.argv, &frame.rval); | ||
1307 | JS_RUNTIME_METER(cx->runtime, nativeCalls); | ||
1308 | #ifdef DEBUG_NOT_THROWING | ||
1309 | if (ok && !alreadyThrowing) | ||
1310 | ASSERT_NOT_THROWING(cx); | ||
1311 | #endif | ||
1312 | } else if (script) { | ||
1313 | /* Use parent scope so js_GetCallObject can find the right "Call". */ | ||
1314 | frame.scopeChain = parent; | ||
1315 | if (JSFUN_HEAVYWEIGHT_TEST(fun->flags)) { | ||
1316 | /* Scope with a call object parented by the callee's parent. */ | ||
1317 | if (!js_GetCallObject(cx, &frame, parent)) { | ||
1318 | ok = JS_FALSE; | ||
1319 | goto out; | ||
1320 | } | ||
1321 | } | ||
1322 | frame.slots = sp - fun->u.i.nvars; | ||
1323 | |||
1324 | ok = js_Interpret(cx); | ||
1325 | } else { | ||
1326 | /* fun might be onerror trying to report a syntax error in itself. */ | ||
1327 | frame.scopeChain = NULL; | ||
1328 | frame.displaySave = NULL; | ||
1329 | ok = JS_TRUE; | ||
1330 | } | ||
1331 | |||
1332 | out: | ||
1333 | if (hookData) { | ||
1334 | hook = cx->debugHooks->callHook; | ||
1335 | if (hook) | ||
1336 | hook(cx, &frame, JS_FALSE, &ok, hookData); | ||
1337 | } | ||
1338 | |||
1339 | /* If frame has a call object, sync values and clear back-pointer. */ | ||
1340 | if (frame.callobj) | ||
1341 | ok &= js_PutCallObject(cx, &frame); | ||
1342 | |||
1343 | /* If frame has an arguments object, sync values and clear back-pointer. */ | ||
1344 | if (frame.argsobj) | ||
1345 | ok &= js_PutArgsObject(cx, &frame); | ||
1346 | |||
1347 | *vp = frame.rval; | ||
1348 | |||
1349 | /* Restore cx->fp now that we're done releasing frame objects. */ | ||
1350 | cx->fp = frame.down; | ||
1351 | |||
1352 | out2: | ||
1353 | /* Pop everything we may have allocated off the stack. */ | ||
1354 | JS_ARENA_RELEASE(&cx->stackPool, mark); | ||
1355 | if (!ok) | ||
1356 | *vp = JSVAL_NULL; | ||
1357 | return ok; | ||
1358 | |||
1359 | bad: | ||
1360 | js_ReportIsNotFunction(cx, vp, flags & JSINVOKE_FUNFLAGS); | ||
1361 | ok = JS_FALSE; | ||
1362 | goto out2; | ||
1363 | } | ||
1364 | |||
1365 | JSBool | ||
1366 | js_InternalInvoke(JSContext *cx, JSObject *obj, jsval fval, uintN flags, | ||
1367 | uintN argc, jsval *argv, jsval *rval) | ||
1368 | { | ||
1369 | jsval *invokevp; | ||
1370 | void *mark; | ||
1371 | JSBool ok; | ||
1372 | |||
1373 | invokevp = js_AllocStack(cx, 2 + argc, &mark); | ||
1374 | if (!invokevp) | ||
1375 | return JS_FALSE; | ||
1376 | |||
1377 | invokevp[0] = fval; | ||
1378 | invokevp[1] = OBJECT_TO_JSVAL(obj); | ||
1379 | memcpy(invokevp + 2, argv, argc * sizeof *argv); | ||
1380 | |||
1381 | ok = js_Invoke(cx, argc, invokevp, flags); | ||
1382 | if (ok) { | ||
1383 | /* | ||
1384 | * Store *rval in the a scoped local root if a scope is open, else in | ||
1385 | * the lastInternalResult pigeon-hole GC root, solely so users of | ||
1386 | * js_InternalInvoke and its direct and indirect (js_ValueToString for | ||
1387 | * example) callers do not need to manage roots for local, temporary | ||
1388 | * references to such results. | ||
1389 | */ | ||
1390 | *rval = *invokevp; | ||
1391 | if (JSVAL_IS_GCTHING(*rval) && *rval != JSVAL_NULL) { | ||
1392 | if (cx->localRootStack) { | ||
1393 | if (js_PushLocalRoot(cx, cx->localRootStack, *rval) < 0) | ||
1394 | ok = JS_FALSE; | ||
1395 | } else { | ||
1396 | cx->weakRoots.lastInternalResult = *rval; | ||
1397 | } | ||
1398 | } | ||
1399 | } | ||
1400 | |||
1401 | js_FreeStack(cx, mark); | ||
1402 | return ok; | ||
1403 | } | ||
1404 | |||
1405 | JSBool | ||
1406 | js_InternalGetOrSet(JSContext *cx, JSObject *obj, jsid id, jsval fval, | ||
1407 | JSAccessMode mode, uintN argc, jsval *argv, jsval *rval) | ||
1408 | { | ||
1409 | JSSecurityCallbacks *callbacks; | ||
1410 | |||
1411 | /* | ||
1412 | * js_InternalInvoke could result in another try to get or set the same id | ||
1413 | * again, see bug 355497. | ||
1414 | */ | ||
1415 | JS_CHECK_RECURSION(cx, return JS_FALSE); | ||
1416 | |||
1417 | /* | ||
1418 | * Check general (not object-ops/class-specific) access from the running | ||
1419 | * script to obj.id only if id has a scripted getter or setter that we're | ||
1420 | * about to invoke. If we don't check this case, nothing else will -- no | ||
1421 | * other native code has the chance to check. | ||
1422 | * | ||
1423 | * Contrast this non-native (scripted) case with native getter and setter | ||
1424 | * accesses, where the native itself must do an access check, if security | ||
1425 | * policies requires it. We make a checkAccess or checkObjectAccess call | ||
1426 | * back to the embedding program only in those cases where we're not going | ||
1427 | * to call an embedding-defined native function, getter, setter, or class | ||
1428 | * hook anyway. Where we do call such a native, there's no need for the | ||
1429 | * engine to impose a separate access check callback on all embeddings -- | ||
1430 | * many embeddings have no security policy at all. | ||
1431 | */ | ||
1432 | JS_ASSERT(mode == JSACC_READ || mode == JSACC_WRITE); | ||
1433 | callbacks = JS_GetSecurityCallbacks(cx); | ||
1434 | if (callbacks && | ||
1435 | callbacks->checkObjectAccess && | ||
1436 | VALUE_IS_FUNCTION(cx, fval) && | ||
1437 | FUN_INTERPRETED(GET_FUNCTION_PRIVATE(cx, JSVAL_TO_OBJECT(fval))) && | ||
1438 | !callbacks->checkObjectAccess(cx, obj, ID_TO_VALUE(id), mode, &fval)) { | ||
1439 | return JS_FALSE; | ||
1440 | } | ||
1441 | |||
1442 | return js_InternalCall(cx, obj, fval, argc, argv, rval); | ||
1443 | } | ||
1444 | |||
1445 | JSBool | ||
1446 | js_Execute(JSContext *cx, JSObject *chain, JSScript *script, | ||
1447 | JSStackFrame *down, uintN flags, jsval *result) | ||
1448 | { | ||
1449 | JSInterpreterHook hook; | ||
1450 | void *hookData, *mark; | ||
1451 | JSStackFrame *oldfp, frame; | ||
1452 | JSObject *obj, *tmp; | ||
1453 | JSBool ok; | ||
1454 | |||
1455 | #ifdef INCLUDE_MOZILLA_DTRACE | ||
1456 | if (JAVASCRIPT_EXECUTE_START_ENABLED()) | ||
1457 | jsdtrace_execute_start(script); | ||
1458 | #endif | ||
1459 | |||
1460 | hook = cx->debugHooks->executeHook; | ||
1461 | hookData = mark = NULL; | ||
1462 | oldfp = cx->fp; | ||
1463 | frame.script = script; | ||
1464 | if (down) { | ||
1465 | /* Propagate arg state for eval and the debugger API. */ | ||
1466 | frame.callobj = down->callobj; | ||
1467 | frame.argsobj = down->argsobj; | ||
1468 | frame.varobj = down->varobj; | ||
1469 | frame.callee = down->callee; | ||
1470 | frame.fun = down->fun; | ||
1471 | frame.thisp = down->thisp; | ||
1472 | if (down->flags & JSFRAME_COMPUTED_THIS) | ||
1473 | flags |= JSFRAME_COMPUTED_THIS; | ||
1474 | frame.argc = down->argc; | ||
1475 | frame.argv = down->argv; | ||
1476 | frame.annotation = down->annotation; | ||
1477 | frame.sharpArray = down->sharpArray; | ||
1478 | JS_ASSERT(script->nfixed == 0); | ||
1479 | } else { | ||
1480 | frame.callobj = frame.argsobj = NULL; | ||
1481 | obj = chain; | ||
1482 | if (cx->options & JSOPTION_VAROBJFIX) { | ||
1483 | while ((tmp = OBJ_GET_PARENT(cx, obj)) != NULL) | ||
1484 | obj = tmp; | ||
1485 | } | ||
1486 | frame.varobj = obj; | ||
1487 | frame.callee = NULL; | ||
1488 | frame.fun = NULL; | ||
1489 | frame.thisp = chain; | ||
1490 | frame.argc = 0; | ||
1491 | frame.argv = NULL; | ||
1492 | frame.annotation = NULL; | ||
1493 | frame.sharpArray = NULL; | ||
1494 | } | ||
1495 | if (script->nslots != 0) { | ||
1496 | frame.slots = js_AllocRawStack(cx, script->nslots, &mark); | ||
1497 | if (!frame.slots) { | ||
1498 | ok = JS_FALSE; | ||
1499 | goto out; | ||
1500 | } | ||
1501 | memset(frame.slots, 0, script->nfixed * sizeof(jsval)); | ||
1502 | } else { | ||
1503 | frame.slots = NULL; | ||
1504 | } | ||
1505 | |||
1506 | frame.rval = JSVAL_VOID; | ||
1507 | frame.down = down; | ||
1508 | frame.scopeChain = chain; | ||
1509 | frame.regs = NULL; | ||
1510 | frame.sharpDepth = 0; | ||
1511 | frame.flags = flags; | ||
1512 | frame.dormantNext = NULL; | ||
1513 | frame.xmlNamespace = NULL; | ||
1514 | frame.blockChain = NULL; | ||
1515 | |||
1516 | /* | ||
1517 | * Here we wrap the call to js_Interpret with code to (conditionally) | ||
1518 | * save and restore the old stack frame chain into a chain of 'dormant' | ||
1519 | * frame chains. Since we are replacing cx->fp, we were running into | ||
1520 | * the problem that if GC was called under this frame, some of the GC | ||
1521 | * things associated with the old frame chain (available here only in | ||
1522 | * the C variable 'oldfp') were not rooted and were being collected. | ||
1523 | * | ||
1524 | * So, now we preserve the links to these 'dormant' frame chains in cx | ||
1525 | * before calling js_Interpret and cleanup afterwards. The GC walks | ||
1526 | * these dormant chains and marks objects in the same way that it marks | ||
1527 | * objects in the primary cx->fp chain. | ||
1528 | */ | ||
1529 | if (oldfp && oldfp != down) { | ||
1530 | JS_ASSERT(!oldfp->dormantNext); | ||
1531 | oldfp->dormantNext = cx->dormantFrameChain; | ||
1532 | cx->dormantFrameChain = oldfp; | ||
1533 | } | ||
1534 | |||
1535 | cx->fp = &frame; | ||
1536 | if (!down) { | ||
1537 | OBJ_TO_OUTER_OBJECT(cx, frame.thisp); | ||
1538 | if (!frame.thisp) { | ||
1539 | ok = JS_FALSE; | ||
1540 | goto out2; | ||
1541 | } | ||
1542 | frame.flags |= JSFRAME_COMPUTED_THIS; | ||
1543 | } | ||
1544 | |||
1545 | if (hook) { | ||
1546 | hookData = hook(cx, &frame, JS_TRUE, 0, | ||
1547 | cx->debugHooks->executeHookData); | ||
1548 | } | ||
1549 | |||
1550 | ok = js_Interpret(cx); | ||
1551 | if (result) | ||
1552 | *result = frame.rval; | ||
1553 | |||
1554 | if (hookData) { | ||
1555 | hook = cx->debugHooks->executeHook; | ||
1556 | if (hook) | ||
1557 | hook(cx, &frame, JS_FALSE, &ok, hookData); | ||
1558 | } | ||
1559 | |||
1560 | out2: | ||
1561 | if (mark) | ||
1562 | js_FreeRawStack(cx, mark); | ||
1563 | cx->fp = oldfp; | ||
1564 | |||
1565 | if (oldfp && oldfp != down) { | ||
1566 | JS_ASSERT(cx->dormantFrameChain == oldfp); | ||
1567 | cx->dormantFrameChain = oldfp->dormantNext; | ||
1568 | oldfp->dormantNext = NULL; | ||
1569 | } | ||
1570 | |||
1571 | out: | ||
1572 | #ifdef INCLUDE_MOZILLA_DTRACE | ||
1573 | if (JAVASCRIPT_EXECUTE_DONE_ENABLED()) | ||
1574 | jsdtrace_execute_done(script); | ||
1575 | #endif | ||
1576 | return ok; | ||
1577 | } | ||
1578 | |||
1579 | JSBool | ||
1580 | js_CheckRedeclaration(JSContext *cx, JSObject *obj, jsid id, uintN attrs, | ||
1581 | JSObject **objp, JSProperty **propp) | ||
1582 | { | ||
1583 | JSObject *obj2; | ||
1584 | JSProperty *prop; | ||
1585 | uintN oldAttrs, report; | ||
1586 | JSBool isFunction; | ||
1587 | jsval value; | ||
1588 | const char *type, *name; | ||
1589 | |||
1590 | if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop)) | ||
1591 | return JS_FALSE; | ||
1592 | if (propp) { | ||
1593 | *objp = obj2; | ||
1594 | *propp = prop; | ||
1595 | } | ||
1596 | if (!prop) | ||
1597 | return JS_TRUE; | ||
1598 | |||
1599 | /* | ||
1600 | * Use prop as a speedup hint to OBJ_GET_ATTRIBUTES, but drop it on error. | ||
1601 | * An assertion at label bad: will insist that it is null. | ||
1602 | */ | ||
1603 | if (!OBJ_GET_ATTRIBUTES(cx, obj2, id, prop, &oldAttrs)) { | ||
1604 | OBJ_DROP_PROPERTY(cx, obj2, prop); | ||
1605 | #ifdef DEBUG | ||
1606 | prop = NULL; | ||
1607 | #endif | ||
1608 | goto bad; | ||
1609 | } | ||
1610 | |||
1611 | /* | ||
1612 | * From here, return true, or else goto bad on failure to null out params. | ||
1613 | * If our caller doesn't want prop, drop it (we don't need it any longer). | ||
1614 | */ | ||
1615 | if (!propp) { | ||
1616 | OBJ_DROP_PROPERTY(cx, obj2, prop); | ||
1617 | prop = NULL; | ||
1618 | } | ||
1619 | |||
1620 | if (attrs == JSPROP_INITIALIZER) { | ||
1621 | /* Allow the new object to override properties. */ | ||
1622 | if (obj2 != obj) | ||
1623 | return JS_TRUE; | ||
1624 | report = JSREPORT_WARNING | JSREPORT_STRICT; | ||
1625 | } else { | ||
1626 | /* We allow redeclaring some non-readonly properties. */ | ||
1627 | if (((oldAttrs | attrs) & JSPROP_READONLY) == 0) { | ||
1628 | /* | ||
1629 | * Allow redeclaration of variables and functions, but insist that | ||
1630 | * the new value is not a getter if the old value was, ditto for | ||
1631 | * setters -- unless prop is impermanent (in which case anyone | ||
1632 | * could delete it and redefine it, willy-nilly). | ||
1633 | */ | ||
1634 | if (!(attrs & (JSPROP_GETTER | JSPROP_SETTER))) | ||
1635 | return JS_TRUE; | ||
1636 | if ((~(oldAttrs ^ attrs) & (JSPROP_GETTER | JSPROP_SETTER)) == 0) | ||
1637 | return JS_TRUE; | ||
1638 | if (!(oldAttrs & JSPROP_PERMANENT)) | ||
1639 | return JS_TRUE; | ||
1640 | } | ||
1641 | |||
1642 | report = JSREPORT_ERROR; | ||
1643 | isFunction = (oldAttrs & (JSPROP_GETTER | JSPROP_SETTER)) != 0; | ||
1644 | if (!isFunction) { | ||
1645 | if (!OBJ_GET_PROPERTY(cx, obj, id, &value)) | ||
1646 | goto bad; | ||
1647 | isFunction = VALUE_IS_FUNCTION(cx, value); | ||
1648 | } | ||
1649 | } | ||
1650 | |||
1651 | type = (attrs == JSPROP_INITIALIZER) | ||
1652 | ? "property" | ||
1653 | : (oldAttrs & attrs & JSPROP_GETTER) | ||
1654 | ? js_getter_str | ||
1655 | : (oldAttrs & attrs & JSPROP_SETTER) | ||
1656 | ? js_setter_str | ||
1657 | : (oldAttrs & JSPROP_READONLY) | ||
1658 | ? js_const_str | ||
1659 | : isFunction | ||
1660 | ? js_function_str | ||
1661 | : js_var_str; | ||
1662 | name = js_ValueToPrintableString(cx, ID_TO_VALUE(id)); | ||
1663 | if (!name) | ||
1664 | goto bad; | ||
1665 | return JS_ReportErrorFlagsAndNumber(cx, report, | ||
1666 | js_GetErrorMessage, NULL, | ||
1667 | JSMSG_REDECLARED_VAR, | ||
1668 | type, name); | ||
1669 | |||
1670 | bad: | ||
1671 | if (propp) { | ||
1672 | *objp = NULL; | ||
1673 | *propp = NULL; | ||
1674 | } | ||
1675 | JS_ASSERT(!prop); | ||
1676 | return JS_FALSE; | ||
1677 | } | ||
1678 | |||
1679 | JSBool | ||
1680 | js_StrictlyEqual(JSContext *cx, jsval lval, jsval rval) | ||
1681 | { | ||
1682 | jsval ltag = JSVAL_TAG(lval), rtag = JSVAL_TAG(rval); | ||
1683 | jsdouble ld, rd; | ||
1684 | |||
1685 | if (ltag == rtag) { | ||
1686 | if (ltag == JSVAL_STRING) { | ||
1687 | JSString *lstr = JSVAL_TO_STRING(lval), | ||
1688 | *rstr = JSVAL_TO_STRING(rval); | ||
1689 | return js_EqualStrings(lstr, rstr); | ||
1690 | } | ||
1691 | if (ltag == JSVAL_DOUBLE) { | ||
1692 | ld = *JSVAL_TO_DOUBLE(lval); | ||
1693 | rd = *JSVAL_TO_DOUBLE(rval); | ||
1694 | return JSDOUBLE_COMPARE(ld, ==, rd, JS_FALSE); | ||
1695 | } | ||
1696 | if (ltag == JSVAL_OBJECT && | ||
1697 | lval != rval && | ||
1698 | !JSVAL_IS_NULL(lval) && | ||
1699 | !JSVAL_IS_NULL(rval)) { | ||
1700 | JSObject *lobj, *robj; | ||
1701 | |||
1702 | lobj = js_GetWrappedObject(cx, JSVAL_TO_OBJECT(lval)); | ||
1703 | robj = js_GetWrappedObject(cx, JSVAL_TO_OBJECT(rval)); | ||
1704 | lval = OBJECT_TO_JSVAL(lobj); | ||
1705 | rval = OBJECT_TO_JSVAL(robj); | ||
1706 | } | ||
1707 | return lval == rval; | ||
1708 | } | ||
1709 | if (ltag == JSVAL_DOUBLE && JSVAL_IS_INT(rval)) { | ||
1710 | ld = *JSVAL_TO_DOUBLE(lval); | ||
1711 | rd = JSVAL_TO_INT(rval); | ||
1712 | return JSDOUBLE_COMPARE(ld, ==, rd, JS_FALSE); | ||
1713 | } | ||
1714 | if (JSVAL_IS_INT(lval) && rtag == JSVAL_DOUBLE) { | ||
1715 | ld = JSVAL_TO_INT(lval); | ||
1716 | rd = *JSVAL_TO_DOUBLE(rval); | ||
1717 | return JSDOUBLE_COMPARE(ld, ==, rd, JS_FALSE); | ||
1718 | } | ||
1719 | return lval == rval; | ||
1720 | } | ||
1721 | |||
1722 | JSBool | ||
1723 | js_InvokeConstructor(JSContext *cx, uintN argc, JSBool clampReturn, jsval *vp) | ||
1724 | { | ||
1725 | JSFunction *fun, *fun2; | ||
1726 | JSObject *obj, *obj2, *proto, *parent; | ||
1727 | jsval lval, rval; | ||
1728 | JSClass *clasp; | ||
1729 | |||
1730 | fun = NULL; | ||
1731 | obj2 = NULL; | ||
1732 | lval = *vp; | ||
1733 | if (!JSVAL_IS_OBJECT(lval) || | ||
1734 | (obj2 = JSVAL_TO_OBJECT(lval)) == NULL || | ||
1735 | /* XXX clean up to avoid special cases above ObjectOps layer */ | ||
1736 | OBJ_GET_CLASS(cx, obj2) == &js_FunctionClass || | ||
1737 | !obj2->map->ops->construct) | ||
1738 | { | ||
1739 | fun = js_ValueToFunction(cx, vp, JSV2F_CONSTRUCT); | ||
1740 | if (!fun) | ||
1741 | return JS_FALSE; | ||
1742 | } | ||
1743 | |||
1744 | clasp = &js_ObjectClass; | ||
1745 | if (!obj2) { | ||
1746 | proto = parent = NULL; | ||
1747 | fun = NULL; | ||
1748 | } else { | ||
1749 | /* | ||
1750 | * Get the constructor prototype object for this function. | ||
1751 | * Use the nominal 'this' parameter slot, vp[1], as a local | ||
1752 | * root to protect this prototype, in case it has no other | ||
1753 | * strong refs. | ||
1754 | */ | ||
1755 | if (!OBJ_GET_PROPERTY(cx, obj2, | ||
1756 | ATOM_TO_JSID(cx->runtime->atomState | ||
1757 | .classPrototypeAtom), | ||
1758 | &vp[1])) { | ||
1759 | return JS_FALSE; | ||
1760 | } | ||
1761 | rval = vp[1]; | ||
1762 | proto = JSVAL_IS_OBJECT(rval) ? JSVAL_TO_OBJECT(rval) : NULL; | ||
1763 | parent = OBJ_GET_PARENT(cx, obj2); | ||
1764 | |||
1765 | if (OBJ_GET_CLASS(cx, obj2) == &js_FunctionClass) { | ||
1766 | fun2 = GET_FUNCTION_PRIVATE(cx, obj2); | ||
1767 | if (!FUN_INTERPRETED(fun2) && fun2->u.n.clasp) | ||
1768 | clasp = fun2->u.n.clasp; | ||
1769 | } | ||
1770 | } | ||
1771 | obj = js_NewObject(cx, clasp, proto, parent, 0); | ||
1772 | if (!obj) | ||
1773 | return JS_FALSE; | ||
1774 | |||
1775 | /* Now we have an object with a constructor method; call it. */ | ||
1776 | vp[1] = OBJECT_TO_JSVAL(obj); | ||
1777 | if (!js_Invoke(cx, argc, vp, JSINVOKE_CONSTRUCT)) { | ||
1778 | cx->weakRoots.newborn[GCX_OBJECT] = NULL; | ||
1779 | return JS_FALSE; | ||
1780 | } | ||
1781 | |||
1782 | /* Check the return value and if it's primitive, force it to be obj. */ | ||
1783 | rval = *vp; | ||
1784 | if (clampReturn && JSVAL_IS_PRIMITIVE(rval)) { | ||
1785 | if (!fun) { | ||
1786 | /* native [[Construct]] returning primitive is error */ | ||
1787 | JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, | ||
1788 | JSMSG_BAD_NEW_RESULT, | ||
1789 | js_ValueToPrintableString(cx, rval)); | ||
1790 | return JS_FALSE; | ||
1791 | } | ||
1792 | *vp = OBJECT_TO_JSVAL(obj); | ||
1793 | } | ||
1794 | |||
1795 | JS_RUNTIME_METER(cx->runtime, constructs); | ||
1796 | return JS_TRUE; | ||
1797 | } | ||
1798 | |||
1799 | JSBool | ||
1800 | js_InternNonIntElementId(JSContext *cx, JSObject *obj, jsval idval, jsid *idp) | ||
1801 | { | ||
1802 | JS_ASSERT(!JSVAL_IS_INT(idval)); | ||
1803 | |||
1804 | #if JS_HAS_XML_SUPPORT | ||
1805 | if (!JSVAL_IS_PRIMITIVE(idval)) { | ||
1806 | if (OBJECT_IS_XML(cx, obj)) { | ||
1807 | *idp = OBJECT_JSVAL_TO_JSID(idval); | ||
1808 | return JS_TRUE; | ||
1809 | } | ||
1810 | if (!js_IsFunctionQName(cx, JSVAL_TO_OBJECT(idval), idp)) | ||
1811 | return JS_FALSE; | ||
1812 | if (*idp != 0) | ||
1813 | return JS_TRUE; | ||
1814 | } | ||
1815 | #endif | ||
1816 | |||
1817 | return js_ValueToStringId(cx, idval, idp); | ||
1818 | } | ||
1819 | |||
1820 | /* | ||
1821 | * Enter the new with scope using an object at sp[-1] and associate the depth | ||
1822 | * of the with block with sp + stackIndex. | ||
1823 | */ | ||
1824 | JS_STATIC_INTERPRET JSBool | ||
1825 | js_EnterWith(JSContext *cx, jsint stackIndex) | ||
1826 | { | ||
1827 | JSStackFrame *fp; | ||
1828 | jsval *sp; | ||
1829 | JSObject *obj, *parent, *withobj; | ||
1830 | |||
1831 | fp = cx->fp; | ||
1832 | sp = fp->regs->sp; | ||
1833 | JS_ASSERT(stackIndex < 0); | ||
1834 | JS_ASSERT(StackBase(fp) <= sp + stackIndex); | ||
1835 | |||
1836 | if (!JSVAL_IS_PRIMITIVE(sp[-1])) { | ||
1837 | obj = JSVAL_TO_OBJECT(sp[-1]); | ||
1838 | } else { | ||
1839 | obj = js_ValueToNonNullObject(cx, sp[-1]); | ||
1840 | if (!obj) | ||
1841 | return JS_FALSE; | ||
1842 | sp[-1] = OBJECT_TO_JSVAL(obj); | ||
1843 | } | ||
1844 | |||
1845 | parent = js_GetScopeChain(cx, fp); | ||
1846 | if (!parent) | ||
1847 | return JS_FALSE; | ||
1848 | |||
1849 | OBJ_TO_INNER_OBJECT(cx, obj); | ||
1850 | if (!obj) | ||
1851 | return JS_FALSE; | ||
1852 | |||
1853 | withobj = js_NewWithObject(cx, obj, parent, | ||
1854 | sp + stackIndex - StackBase(fp)); | ||
1855 | if (!withobj) | ||
1856 | return JS_FALSE; | ||
1857 | |||
1858 | fp->scopeChain = withobj; | ||
1859 | js_DisablePropertyCache(cx); | ||
1860 | return JS_TRUE; | ||
1861 | } | ||
1862 | |||
1863 | JS_STATIC_INTERPRET void | ||
1864 | js_LeaveWith(JSContext *cx) | ||
1865 | { | ||
1866 | JSObject *withobj; | ||
1867 | |||
1868 | withobj = cx->fp->scopeChain; | ||
1869 | JS_ASSERT(OBJ_GET_CLASS(cx, withobj) == &js_WithClass); | ||
1870 | JS_ASSERT(OBJ_GET_PRIVATE(cx, withobj) == cx->fp); | ||
1871 | JS_ASSERT(OBJ_BLOCK_DEPTH(cx, withobj) >= 0); | ||
1872 | cx->fp->scopeChain = OBJ_GET_PARENT(cx, withobj); | ||
1873 | JS_SetPrivate(cx, withobj, NULL); | ||
1874 | js_EnablePropertyCache(cx); | ||
1875 | } | ||
1876 | |||
1877 | JSClass * | ||
1878 | js_IsActiveWithOrBlock(JSContext *cx, JSObject *obj, int stackDepth) | ||
1879 | { | ||
1880 | JSClass *clasp; | ||
1881 | |||
1882 | clasp = OBJ_GET_CLASS(cx, obj); | ||
1883 | if ((clasp == &js_WithClass || clasp == &js_BlockClass) && | ||
1884 | OBJ_GET_PRIVATE(cx, obj) == cx->fp && | ||
1885 | OBJ_BLOCK_DEPTH(cx, obj) >= stackDepth) { | ||
1886 | return clasp; | ||
1887 | } | ||
1888 | return NULL; | ||
1889 | } | ||
1890 | |||
1891 | JS_STATIC_INTERPRET jsint | ||
1892 | js_CountWithBlocks(JSContext *cx, JSStackFrame *fp) | ||
1893 | { | ||
1894 | jsint n; | ||
1895 | JSObject *obj; | ||
1896 | JSClass *clasp; | ||
1897 | |||
1898 | n = 0; | ||
1899 | for (obj = fp->scopeChain; | ||
1900 | (clasp = js_IsActiveWithOrBlock(cx, obj, 0)) != NULL; | ||
1901 | obj = OBJ_GET_PARENT(cx, obj)) { | ||
1902 | if (clasp == &js_WithClass) | ||
1903 | ++n; | ||
1904 | } | ||
1905 | return n; | ||
1906 | } | ||
1907 | |||
1908 | /* | ||
1909 | * Unwind block and scope chains to match the given depth. The function sets | ||
1910 | * fp->sp on return to stackDepth. | ||
1911 | */ | ||
1912 | JSBool | ||
1913 | js_UnwindScope(JSContext *cx, JSStackFrame *fp, jsint stackDepth, | ||
1914 | JSBool normalUnwind) | ||
1915 | { | ||
1916 | JSObject *obj; | ||
1917 | JSClass *clasp; | ||
1918 | |||
1919 | JS_ASSERT(stackDepth >= 0); | ||
1920 | JS_ASSERT(StackBase(fp) + stackDepth <= fp->regs->sp); | ||
1921 | |||
1922 | for (obj = fp->blockChain; obj; obj = OBJ_GET_PARENT(cx, obj)) { | ||
1923 | JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_BlockClass); | ||
1924 | if (OBJ_BLOCK_DEPTH(cx, obj) < stackDepth) | ||
1925 | break; | ||
1926 | } | ||
1927 | fp->blockChain = obj; | ||
1928 | |||
1929 | for (;;) { | ||
1930 | obj = fp->scopeChain; | ||
1931 | clasp = js_IsActiveWithOrBlock(cx, obj, stackDepth); | ||
1932 | if (!clasp) | ||
1933 | break; | ||
1934 | if (clasp == &js_BlockClass) { | ||
1935 | /* Don't fail until after we've updated all stacks. */ | ||
1936 | normalUnwind &= js_PutBlockObject(cx, normalUnwind); | ||
1937 | } else { | ||
1938 | js_LeaveWith(cx); | ||
1939 | } | ||
1940 | } | ||
1941 | |||
1942 | fp->regs->sp = StackBase(fp) + stackDepth; | ||
1943 | return normalUnwind; | ||
1944 | } | ||
1945 | |||
1946 | JS_STATIC_INTERPRET JSBool | ||
1947 | js_DoIncDec(JSContext *cx, const JSCodeSpec *cs, jsval *vp, jsval *vp2) | ||
1948 | { | ||
1949 | jsval v; | ||
1950 | jsdouble d; | ||
1951 | |||
1952 | v = *vp; | ||
1953 | if (JSVAL_IS_DOUBLE(v)) { | ||
1954 | d = *JSVAL_TO_DOUBLE(v); | ||
1955 | } else if (JSVAL_IS_INT(v)) { | ||
1956 | d = JSVAL_TO_INT(v); | ||
1957 | } else { | ||
1958 | d = js_ValueToNumber(cx, vp); | ||
1959 | if (JSVAL_IS_NULL(*vp)) | ||
1960 | return JS_FALSE; | ||
1961 | JS_ASSERT(JSVAL_IS_NUMBER(*vp) || *vp == JSVAL_TRUE); | ||
1962 | |||
1963 | /* Store the result of v conversion back in vp for post increments. */ | ||
1964 | if ((cs->format & JOF_POST) && | ||
1965 | *vp == JSVAL_TRUE | ||
1966 | && !js_NewNumberInRootedValue(cx, d, vp)) { | ||
1967 | return JS_FALSE; | ||
1968 | } | ||
1969 | } | ||
1970 | |||
1971 | (cs->format & JOF_INC) ? d++ : d--; | ||
1972 | if (!js_NewNumberInRootedValue(cx, d, vp2)) | ||
1973 | return JS_FALSE; | ||
1974 | |||
1975 | if (!(cs->format & JOF_POST)) | ||
1976 | *vp = *vp2; | ||
1977 | return JS_TRUE; | ||
1978 | } | ||
1979 | |||
1980 | #ifdef DEBUG | ||
1981 | |||
1982 | JS_STATIC_INTERPRET void | ||
1983 | js_TraceOpcode(JSContext *cx, jsint len) | ||
1984 | { | ||
1985 | FILE *tracefp; | ||
1986 | JSStackFrame *fp; | ||
1987 | JSFrameRegs *regs; | ||
1988 | JSOp prevop; | ||
1989 | intN ndefs, n, nuses; | ||
1990 | jsval *siter; | ||
1991 | JSString *str; | ||
1992 | JSOp op; | ||
1993 | |||
1994 | tracefp = (FILE *) cx->tracefp; | ||
1995 | JS_ASSERT(tracefp); | ||
1996 | fp = cx->fp; | ||
1997 | regs = fp->regs; | ||
1998 | if (len != 0) { | ||
1999 | prevop = (JSOp) regs->pc[-len]; | ||
2000 | ndefs = js_CodeSpec[prevop].ndefs; | ||
2001 | if (ndefs != 0) { | ||
2002 | if (prevop == JSOP_FORELEM && regs->sp[-1] == JSVAL_FALSE) | ||
2003 | --ndefs; | ||
2004 | for (n = -ndefs; n < 0; n++) { | ||
2005 | char *bytes = js_DecompileValueGenerator(cx, n, regs->sp[n], | ||
2006 | NULL); | ||
2007 | if (bytes) { | ||
2008 | fprintf(tracefp, "%s %s", | ||
2009 | (n == -ndefs) ? " output:" : ",", | ||
2010 | bytes); | ||
2011 | JS_free(cx, bytes); | ||
2012 | } | ||
2013 | } | ||
2014 | fprintf(tracefp, " @ %u\n", (uintN) (regs->sp - StackBase(fp))); | ||
2015 | } | ||
2016 | fprintf(tracefp, " stack: "); | ||
2017 | for (siter = StackBase(fp); siter < regs->sp; siter++) { | ||
2018 | str = js_ValueToString(cx, *siter); | ||
2019 | if (!str) | ||
2020 | fputs("<null>", tracefp); | ||
2021 | else | ||
2022 | js_FileEscapedString(tracefp, str, 0); | ||
2023 | fputc(' ', tracefp); | ||
2024 | } | ||
2025 | fputc('\n', tracefp); | ||
2026 | } | ||
2027 | |||
2028 | fprintf(tracefp, "%4u: ", js_PCToLineNumber(cx, fp->script, regs->pc)); | ||
2029 | js_Disassemble1(cx, fp->script, regs->pc, | ||
2030 | PTRDIFF(regs->pc, fp->script->code, jsbytecode), | ||
2031 | JS_FALSE, tracefp); | ||
2032 | op = (JSOp) *regs->pc; | ||
2033 | nuses = js_CodeSpec[op].nuses; | ||
2034 | if (nuses != 0) { | ||
2035 | for (n = -nuses; n < 0; n++) { | ||
2036 | char *bytes = js_DecompileValueGenerator(cx, n, regs->sp[n], | ||
2037 | NULL); | ||
2038 | if (bytes) { | ||
2039 | fprintf(tracefp, "%s %s", | ||
2040 | (n == -nuses) ? " inputs:" : ",", | ||
2041 | bytes); | ||
2042 | JS_free(cx, bytes); | ||
2043 | } | ||
2044 | } | ||
2045 | fprintf(tracefp, " @ %u\n", (uintN) (regs->sp - StackBase(fp))); | ||
2046 | } | ||
2047 | } | ||
2048 | |||
2049 | #endif /* DEBUG */ | ||
2050 | |||
2051 | #ifdef JS_OPMETER | ||
2052 | |||
2053 | # include <stdlib.h> | ||
2054 | |||
2055 | # define HIST_NSLOTS 8 | ||
2056 | |||
2057 | /* | ||
2058 | * The second dimension is hardcoded at 256 because we know that many bits fit | ||
2059 | * in a byte, and mainly to optimize away multiplying by JSOP_LIMIT to address | ||
2060 | * any particular row. | ||
2061 | */ | ||
2062 | static uint32 succeeds[JSOP_LIMIT][256]; | ||
2063 | static uint32 slot_ops[JSOP_LIMIT][HIST_NSLOTS]; | ||
2064 | |||
2065 | JS_STATIC_INTERPRET void | ||
2066 | js_MeterOpcodePair(JSOp op1, JSOp op2) | ||
2067 | { | ||
2068 | if (op1 != JSOP_STOP) | ||
2069 | ++succeeds[op1][op2]; | ||
2070 | } | ||
2071 | |||
2072 | JS_STATIC_INTERPRET void | ||
2073 | js_MeterSlotOpcode(JSOp op, uint32 slot) | ||
2074 | { | ||
2075 | if (slot < HIST_NSLOTS) | ||
2076 | ++slot_ops[op][slot]; | ||
2077 | } | ||
2078 | |||
2079 | typedef struct Edge { | ||
2080 | const char *from; | ||
2081 | const char *to; | ||
2082 | uint32 count; | ||
2083 | } Edge; | ||
2084 | |||
2085 | static int | ||
2086 | compare_edges(const void *a, const void *b) | ||
2087 | { | ||
2088 | const Edge *ea = (const Edge *) a; | ||
2089 | const Edge *eb = (const Edge *) b; | ||
2090 | |||
2091 | return (int32)eb->count - (int32)ea->count; | ||
2092 | } | ||
2093 | |||
2094 | void | ||
2095 | js_DumpOpMeters() | ||
2096 | { | ||
2097 | const char *name, *from, *style; | ||
2098 | FILE *fp; | ||
2099 | uint32 total, count; | ||
2100 | uint32 i, j, nedges; | ||
2101 | Edge *graph; | ||
2102 | |||
2103 | name = getenv("JS_OPMETER_FILE"); | ||
2104 | if (!name) | ||
2105 | name = "/tmp/ops.dot"; | ||
2106 | fp = fopen(name, "w"); | ||
2107 | if (!fp) { | ||
2108 | perror(name); | ||
2109 | return; | ||
2110 | } | ||
2111 | |||
2112 | total = nedges = 0; | ||
2113 | for (i = 0; i < JSOP_LIMIT; i++) { | ||
2114 | for (j = 0; j < JSOP_LIMIT; j++) { | ||
2115 | count = succeeds[i][j]; | ||
2116 | if (count != 0) { | ||
2117 | total += count; | ||
2118 | ++nedges; | ||
2119 | } | ||
2120 | } | ||
2121 | } | ||
2122 | |||
2123 | # define SIGNIFICANT(count,total) (200. * (count) >= (total)) | ||
2124 | |||
2125 | graph = (Edge *) calloc(nedges, sizeof graph[0]); | ||
2126 | for (i = nedges = 0; i < JSOP_LIMIT; i++) { | ||
2127 | from = js_CodeName[i]; | ||
2128 | for (j = 0; j < JSOP_LIMIT; j++) { | ||
2129 | count = succeeds[i][j]; | ||
2130 | if (count != 0 && SIGNIFICANT(count, total)) { | ||
2131 | graph[nedges].from = from; | ||
2132 | graph[nedges].to = js_CodeName[j]; | ||
2133 | graph[nedges].count = count; | ||
2134 | ++nedges; | ||
2135 | } | ||
2136 | } | ||
2137 | } | ||
2138 | qsort(graph, nedges, sizeof(Edge), compare_edges); | ||
2139 | |||
2140 | # undef SIGNIFICANT | ||
2141 | |||
2142 | fputs("digraph {\n", fp); | ||
2143 | for (i = 0, style = NULL; i < nedges; i++) { | ||
2144 | JS_ASSERT(i == 0 || graph[i-1].count >= graph[i].count); | ||
2145 | if (!style || graph[i-1].count != graph[i].count) { | ||
2146 | style = (i > nedges * .75) ? "dotted" : | ||
2147 | (i > nedges * .50) ? "dashed" : | ||
2148 | (i > nedges * .25) ? "solid" : "bold"; | ||
2149 | } | ||
2150 | fprintf(fp, " %s -> %s [label=\"%lu\" style=%s]\n", | ||
2151 | graph[i].from, graph[i].to, | ||
2152 | (unsigned long)graph[i].count, style); | ||
2153 | } | ||
2154 | free(graph); | ||
2155 | fputs("}\n", fp); | ||
2156 | fclose(fp); | ||
2157 | |||
2158 | name = getenv("JS_OPMETER_HIST"); | ||
2159 | if (!name) | ||
2160 | name = "/tmp/ops.hist"; | ||
2161 | fp = fopen(name, "w"); | ||
2162 | if (!fp) { | ||
2163 | perror(name); | ||
2164 | return; | ||
2165 | } | ||
2166 | fputs("bytecode", fp); | ||
2167 | for (j = 0; j < HIST_NSLOTS; j++) | ||
2168 | fprintf(fp, " slot %1u", (unsigned)j); | ||
2169 | putc('\n', fp); | ||
2170 | fputs("========", fp); | ||
2171 | for (j = 0; j < HIST_NSLOTS; j++) | ||
2172 | fputs(" =======", fp); | ||
2173 | putc('\n', fp); | ||
2174 | for (i = 0; i < JSOP_LIMIT; i++) { | ||
2175 | for (j = 0; j < HIST_NSLOTS; j++) { | ||
2176 | if (slot_ops[i][j] != 0) { | ||
2177 | /* Reuse j in the next loop, since we break after. */ | ||
2178 | fprintf(fp, "%-8.8s", js_CodeName[i]); | ||
2179 | for (j = 0; j < HIST_NSLOTS; j++) | ||
2180 | fprintf(fp, " %7lu", (unsigned long)slot_ops[i][j]); | ||
2181 | putc('\n', fp); | ||
2182 | break; | ||
2183 | } | ||
2184 | } | ||
2185 | } | ||
2186 | fclose(fp); | ||
2187 | } | ||
2188 | |||
2189 | #endif /* JS_OPSMETER */ | ||
2190 | |||
2191 | #endif /* !JS_LONE_INTERPRET ^ defined jsinvoke_cpp___ */ | ||
2192 | |||
2193 | #ifndef jsinvoke_cpp___ | ||
2194 | |||
2195 | #define PUSH(v) (*regs.sp++ = (v)) | ||
2196 | #define PUSH_OPND(v) PUSH(v) | ||
2197 | #define STORE_OPND(n,v) (regs.sp[n] = (v)) | ||
2198 | #define POP() (*--regs.sp) | ||
2199 | #define POP_OPND() POP() | ||
2200 | #define FETCH_OPND(n) (regs.sp[n]) | ||
2201 | |||
2202 | /* | ||
2203 | * Push the jsdouble d using sp from the lexical environment. Try to convert d | ||
2204 | * to a jsint that fits in a jsval, otherwise GC-alloc space for it and push a | ||
2205 | * reference. | ||
2206 | */ | ||
2207 | #define STORE_NUMBER(cx, n, d) \ | ||
2208 | JS_BEGIN_MACRO \ | ||
2209 | jsint i_; \ | ||
2210 | \ | ||
2211 | if (JSDOUBLE_IS_INT(d, i_) && INT_FITS_IN_JSVAL(i_)) \ | ||
2212 | regs.sp[n] = INT_TO_JSVAL(i_); \ | ||
2213 | else if (!js_NewDoubleInRootedValue(cx, d, ®s.sp[n])) \ | ||
2214 | goto error; \ | ||
2215 | JS_END_MACRO | ||
2216 | |||
2217 | #define STORE_INT(cx, n, i) \ | ||
2218 | JS_BEGIN_MACRO \ | ||
2219 | if (INT_FITS_IN_JSVAL(i)) \ | ||
2220 | regs.sp[n] = INT_TO_JSVAL(i); \ | ||
2221 | else if (!js_NewDoubleInRootedValue(cx, (jsdouble) (i), ®s.sp[n])) \ | ||
2222 | goto error; \ | ||
2223 | JS_END_MACRO | ||
2224 | |||
2225 | #define STORE_UINT(cx, n, u) \ | ||
2226 | JS_BEGIN_MACRO \ | ||
2227 | if ((u) <= JSVAL_INT_MAX) \ | ||
2228 | regs.sp[n] = INT_TO_JSVAL(u); \ | ||
2229 | else if (!js_NewDoubleInRootedValue(cx, (jsdouble) (u), ®s.sp[n])) \ | ||
2230 | goto error; \ | ||
2231 | JS_END_MACRO | ||
2232 | |||
2233 | #define FETCH_NUMBER(cx, n, d) \ | ||
2234 | JS_BEGIN_MACRO \ | ||
2235 | jsval v_; \ | ||
2236 | \ | ||
2237 | v_ = FETCH_OPND(n); \ | ||
2238 | VALUE_TO_NUMBER(cx, n, v_, d); \ | ||
2239 | JS_END_MACRO | ||
2240 | |||
2241 | #define FETCH_INT(cx, n, i) \ | ||
2242 | JS_BEGIN_MACRO \ | ||
2243 | jsval v_; \ | ||
2244 | \ | ||
2245 | v_= FETCH_OPND(n); \ | ||
2246 | if (JSVAL_IS_INT(v_)) { \ | ||
2247 | i = JSVAL_TO_INT(v_); \ | ||
2248 | } else { \ | ||
2249 | i = js_ValueToECMAInt32(cx, ®s.sp[n]); \ | ||
2250 | if (JSVAL_IS_NULL(regs.sp[n])) \ | ||
2251 | goto error; \ | ||
2252 | } \ | ||
2253 | JS_END_MACRO | ||
2254 | |||
2255 | #define FETCH_UINT(cx, n, ui) \ | ||
2256 | JS_BEGIN_MACRO \ | ||
2257 | jsval v_; \ | ||
2258 | \ | ||
2259 | v_= FETCH_OPND(n); \ | ||
2260 | if (JSVAL_IS_INT(v_)) { \ | ||
2261 | ui = (uint32) JSVAL_TO_INT(v_); \ | ||
2262 | } else { \ | ||
2263 | ui = js_ValueToECMAUint32(cx, ®s.sp[n]); \ | ||
2264 | if (JSVAL_IS_NULL(regs.sp[n])) \ | ||
2265 | goto error; \ | ||
2266 | } \ | ||
2267 | JS_END_MACRO | ||
2268 | |||
2269 | /* | ||
2270 | * Optimized conversion macros that test for the desired type in v before | ||
2271 | * homing sp and calling a conversion function. | ||
2272 | */ | ||
2273 | #define VALUE_TO_NUMBER(cx, n, v, d) \ | ||
2274 | JS_BEGIN_MACRO \ | ||
2275 | JS_ASSERT(v == regs.sp[n]); \ | ||
2276 | if (JSVAL_IS_INT(v)) { \ | ||
2277 | d = (jsdouble)JSVAL_TO_INT(v); \ | ||
2278 | } else if (JSVAL_IS_DOUBLE(v)) { \ | ||
2279 | d = *JSVAL_TO_DOUBLE(v); \ | ||
2280 | } else { \ | ||
2281 | d = js_ValueToNumber(cx, ®s.sp[n]); \ | ||
2282 | if (JSVAL_IS_NULL(regs.sp[n])) \ | ||
2283 | goto error; \ | ||
2284 | JS_ASSERT(JSVAL_IS_NUMBER(regs.sp[n]) || \ | ||
2285 | regs.sp[n] == JSVAL_TRUE); \ | ||
2286 | } \ | ||
2287 | JS_END_MACRO | ||
2288 | |||
2289 | #define POP_BOOLEAN(cx, v, b) \ | ||
2290 | JS_BEGIN_MACRO \ | ||
2291 | v = FETCH_OPND(-1); \ | ||
2292 | if (v == JSVAL_NULL) { \ | ||
2293 | b = JS_FALSE; \ | ||
2294 | } else if (JSVAL_IS_BOOLEAN(v)) { \ | ||
2295 | b = JSVAL_TO_BOOLEAN(v); \ | ||
2296 | } else { \ | ||
2297 | b = js_ValueToBoolean(v); \ | ||
2298 | } \ | ||
2299 | regs.sp--; \ | ||
2300 | JS_END_MACRO | ||
2301 | |||
2302 | #define VALUE_TO_OBJECT(cx, n, v, obj) \ | ||
2303 | JS_BEGIN_MACRO \ | ||
2304 | if (!JSVAL_IS_PRIMITIVE(v)) { \ | ||
2305 | obj = JSVAL_TO_OBJECT(v); \ | ||
2306 | } else { \ | ||
2307 | obj = js_ValueToNonNullObject(cx, v); \ | ||
2308 | if (!obj) \ | ||
2309 | goto error; \ | ||
2310 | STORE_OPND(n, OBJECT_TO_JSVAL(obj)); \ | ||
2311 | } \ | ||
2312 | JS_END_MACRO | ||
2313 | |||
2314 | #define FETCH_OBJECT(cx, n, v, obj) \ | ||
2315 | JS_BEGIN_MACRO \ | ||
2316 | v = FETCH_OPND(n); \ | ||
2317 | VALUE_TO_OBJECT(cx, n, v, obj); \ | ||
2318 | JS_END_MACRO | ||
2319 | |||
2320 | #define DEFAULT_VALUE(cx, n, hint, v) \ | ||
2321 | JS_BEGIN_MACRO \ | ||
2322 | JS_ASSERT(!JSVAL_IS_PRIMITIVE(v)); \ | ||
2323 | JS_ASSERT(v == regs.sp[n]); \ | ||
2324 | if (!OBJ_DEFAULT_VALUE(cx, JSVAL_TO_OBJECT(v), hint, ®s.sp[n])) \ | ||
2325 | goto error; \ | ||
2326 | v = regs.sp[n]; \ | ||
2327 | JS_END_MACRO | ||
2328 | |||
2329 | /* | ||
2330 | * Quickly test if v is an int from the [-2**29, 2**29) range, that is, when | ||
2331 | * the lowest bit of v is 1 and the bits 30 and 31 are both either 0 or 1. For | ||
2332 | * such v we can do increment or decrement via adding or subtracting two | ||
2333 | * without checking that the result overflows JSVAL_INT_MIN or JSVAL_INT_MAX. | ||
2334 | */ | ||
2335 | #define CAN_DO_FAST_INC_DEC(v) (((((v) << 1) ^ v) & 0x80000001) == 1) | ||
2336 | |||
2337 | JS_STATIC_ASSERT(JSVAL_INT == 1); | ||
2338 | JS_STATIC_ASSERT(!CAN_DO_FAST_INC_DEC(INT_TO_JSVAL(JSVAL_INT_MIN))); | ||
2339 | JS_STATIC_ASSERT(!CAN_DO_FAST_INC_DEC(INT_TO_JSVAL(JSVAL_INT_MAX))); | ||
2340 | |||
2341 | /* | ||
2342 | * Conditional assert to detect failure to clear a pending exception that is | ||
2343 | * suppressed (or unintentional suppression of a wanted exception). | ||
2344 | */ | ||
2345 | #if defined DEBUG_brendan || defined DEBUG_mrbkap || defined DEBUG_shaver | ||
2346 | # define DEBUG_NOT_THROWING 1 | ||
2347 | #endif | ||
2348 | |||
2349 | #ifdef DEBUG_NOT_THROWING | ||
2350 | # define ASSERT_NOT_THROWING(cx) JS_ASSERT(!(cx)->throwing) | ||
2351 | #else | ||
2352 | # define ASSERT_NOT_THROWING(cx) /* nothing */ | ||
2353 | #endif | ||
2354 | |||
2355 | /* | ||
2356 | * Define JS_OPMETER to instrument bytecode succession, generating a .dot file | ||
2357 | * on shutdown that shows the graph of significant predecessor/successor pairs | ||
2358 | * executed, where the edge labels give the succession counts. The .dot file | ||
2359 | * is named by the JS_OPMETER_FILE envariable, and defaults to /tmp/ops.dot. | ||
2360 | * | ||
2361 | * Bonus feature: JS_OPMETER also enables counters for stack-addressing ops | ||
2362 | * such as JSOP_GETLOCAL, JSOP_INCARG, via METER_SLOT_OP. The resulting counts | ||
2363 | * are written to JS_OPMETER_HIST, defaulting to /tmp/ops.hist. | ||
2364 | */ | ||
2365 | #ifndef JS_OPMETER | ||
2366 | # define METER_OP_INIT(op) /* nothing */ | ||
2367 | # define METER_OP_PAIR(op1,op2) /* nothing */ | ||
2368 | # define METER_SLOT_OP(op,slot) /* nothing */ | ||
2369 | #else | ||
2370 | |||
2371 | /* | ||
2372 | * The second dimension is hardcoded at 256 because we know that many bits fit | ||
2373 | * in a byte, and mainly to optimize away multiplying by JSOP_LIMIT to address | ||
2374 | * any particular row. | ||
2375 | */ | ||
2376 | # define METER_OP_INIT(op) ((op) = JSOP_STOP) | ||
2377 | # define METER_OP_PAIR(op1,op2) (js_MeterOpcodePair(op1, op2)) | ||
2378 | # define METER_SLOT_OP(op,slot) (js_MeterSlotOpcode(op, slot)) | ||
2379 | |||
2380 | #endif | ||
2381 | |||
2382 | #define MAX_INLINE_CALL_COUNT 3000 | ||
2383 | |||
2384 | /* | ||
2385 | * Threaded interpretation via computed goto appears to be well-supported by | ||
2386 | * GCC 3 and higher. IBM's C compiler when run with the right options (e.g., | ||
2387 | * -qlanglvl=extended) also supports threading. Ditto the SunPro C compiler. | ||
2388 | * Currently it's broken for JS_VERSION < 160, though this isn't worth fixing. | ||
2389 | * Add your compiler support macros here. | ||
2390 | */ | ||
2391 | #ifndef JS_THREADED_INTERP | ||
2392 | # if JS_VERSION >= 160 && ( \ | ||
2393 | __GNUC__ >= 3 || \ | ||
2394 | (__IBMC__ >= 700 && defined __IBM_COMPUTED_GOTO) || \ | ||
2395 | __SUNPRO_C >= 0x570) | ||
2396 | # define JS_THREADED_INTERP 1 | ||
2397 | # else | ||
2398 | # define JS_THREADED_INTERP 0 | ||
2399 | # endif | ||
2400 | #endif | ||
2401 | |||
2402 | /* | ||
2403 | * Interpreter assumes the following to implement condition-free interrupt | ||
2404 | * implementation when !JS_THREADED_INTERP. | ||
2405 | */ | ||
2406 | JS_STATIC_ASSERT(JSOP_INTERRUPT == 0); | ||
2407 | |||
2408 | /* | ||
2409 | * Ensure that the intrepreter switch can close call-bytecode cases in the | ||
2410 | * same way as non-call bytecodes. | ||
2411 | */ | ||
2412 | JS_STATIC_ASSERT(JSOP_NAME_LENGTH == JSOP_CALLNAME_LENGTH); | ||
2413 | JS_STATIC_ASSERT(JSOP_GETGVAR_LENGTH == JSOP_CALLGVAR_LENGTH); | ||
2414 | JS_STATIC_ASSERT(JSOP_GETUPVAR_LENGTH == JSOP_CALLUPVAR_LENGTH); | ||
2415 | JS_STATIC_ASSERT(JSOP_GETARG_LENGTH == JSOP_CALLARG_LENGTH); | ||
2416 | JS_STATIC_ASSERT(JSOP_GETLOCAL_LENGTH == JSOP_CALLLOCAL_LENGTH); | ||
2417 | JS_STATIC_ASSERT(JSOP_XMLNAME_LENGTH == JSOP_CALLXMLNAME_LENGTH); | ||
2418 | |||
2419 | /* | ||
2420 | * Same for JSOP_SETNAME and JSOP_SETPROP, which differ only slightly but | ||
2421 | * remain distinct for the decompiler. Ditto for JSOP_NULL{,THIS}. | ||
2422 | */ | ||
2423 | JS_STATIC_ASSERT(JSOP_SETNAME_LENGTH == JSOP_SETPROP_LENGTH); | ||
2424 | JS_STATIC_ASSERT(JSOP_NULL_LENGTH == JSOP_NULLTHIS_LENGTH); | ||
2425 | |||
2426 | /* See TRY_BRANCH_AFTER_COND. */ | ||
2427 | JS_STATIC_ASSERT(JSOP_IFNE_LENGTH == JSOP_IFEQ_LENGTH); | ||
2428 | JS_STATIC_ASSERT(JSOP_IFNE == JSOP_IFEQ + 1); | ||
2429 | |||
2430 | /* For the fastest case inder JSOP_INCNAME, etc. */ | ||
2431 | JS_STATIC_ASSERT(JSOP_INCNAME_LENGTH == JSOP_DECNAME_LENGTH); | ||
2432 | JS_STATIC_ASSERT(JSOP_INCNAME_LENGTH == JSOP_NAMEINC_LENGTH); | ||
2433 | JS_STATIC_ASSERT(JSOP_INCNAME_LENGTH == JSOP_NAMEDEC_LENGTH); | ||
2434 | |||
2435 | JSBool | ||
2436 | js_Interpret(JSContext *cx) | ||
2437 | { | ||
2438 | JSRuntime *rt; | ||
2439 | JSStackFrame *fp; | ||
2440 | JSScript *script; | ||
2441 | uintN inlineCallCount; | ||
2442 | JSAtom **atoms; | ||
2443 | JSVersion currentVersion, originalVersion; | ||
2444 | JSFrameRegs regs; | ||
2445 | JSObject *obj, *obj2, *parent; | ||
2446 | JSBool ok, cond; | ||
2447 | jsint len; | ||
2448 | jsbytecode *endpc, *pc2; | ||
2449 | JSOp op, op2; | ||
2450 | jsatomid index; | ||
2451 | JSAtom *atom; | ||
2452 | uintN argc, attrs, flags; | ||
2453 | uint32 slot; | ||
2454 | jsval *vp, lval, rval, ltmp, rtmp; | ||
2455 | jsid id; | ||
2456 | JSProperty *prop; | ||
2457 | JSScopeProperty *sprop; | ||
2458 | JSString *str, *str2; | ||
2459 | jsint i, j; | ||
2460 | jsdouble d, d2; | ||
2461 | JSClass *clasp; | ||
2462 | JSFunction *fun; | ||
2463 | JSType type; | ||
2464 | #if JS_THREADED_INTERP | ||
2465 | register void * const *jumpTable; | ||
2466 | #else | ||
2467 | register uint32 switchMask; | ||
2468 | uintN switchOp; | ||
2469 | #endif | ||
2470 | jsint low, high, off, npairs; | ||
2471 | JSBool match; | ||
2472 | #if JS_HAS_GETTER_SETTER | ||
2473 | JSPropertyOp getter, setter; | ||
2474 | #endif | ||
2475 | JSAutoResolveFlags rf(cx, JSRESOLVE_INFER); | ||
2476 | |||
2477 | #ifdef __GNUC__ | ||
2478 | # define JS_EXTENSION __extension__ | ||
2479 | # define JS_EXTENSION_(s) __extension__ ({ s; }) | ||
2480 | #else | ||
2481 | # define JS_EXTENSION | ||
2482 | # define JS_EXTENSION_(s) s | ||
2483 | #endif | ||
2484 | |||
2485 | #if JS_THREADED_INTERP | ||
2486 | static void *const normalJumpTable[] = { | ||
2487 | # define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \ | ||
2488 | JS_EXTENSION &&L_##op, | ||
2489 | # include "jsopcode.tbl" | ||
2490 | # undef OPDEF | ||
2491 | }; | ||
2492 | |||
2493 | #ifdef JS_TRACER | ||
2494 | static void *const recordingJumpTable[] = { | ||
2495 | # define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \ | ||
2496 | JS_EXTENSION &&R_##op, | ||
2497 | # include "jsopcode.tbl" | ||
2498 | # undef OPDEF | ||
2499 | }; | ||
2500 | #endif /* JS_TRACER */ | ||
2501 | |||
2502 | static void *const interruptJumpTable[] = { | ||
2503 | # define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \ | ||
2504 | JS_EXTENSION &&L_JSOP_INTERRUPT, | ||
2505 | # include "jsopcode.tbl" | ||
2506 | # undef OPDEF | ||
2507 | }; | ||
2508 | |||
2509 | METER_OP_INIT(op); /* to nullify first METER_OP_PAIR */ | ||
2510 | |||
2511 | # ifdef JS_TRACER | ||
2512 | # define CHECK_RECORDER() JS_BEGIN_MACRO \ | ||
2513 | JS_ASSERT(!TRACE_RECORDER(cx) ^ \ | ||
2514 | (jumpTable == recordingJumpTable)); \ | ||
2515 | JS_END_MACRO | ||
2516 | # else | ||
2517 | # define CHECK_RECORDER() ((void)0) | ||
2518 | # endif | ||
2519 | |||
2520 | # define DO_OP() JS_BEGIN_MACRO \ | ||
2521 | CHECK_RECORDER(); \ | ||
2522 | JS_EXTENSION_(goto *jumpTable[op]); \ | ||
2523 | JS_END_MACRO | ||
2524 | # define DO_NEXT_OP(n) JS_BEGIN_MACRO \ | ||
2525 | METER_OP_PAIR(op, regs.pc[n]); \ | ||
2526 | op = (JSOp) *(regs.pc += (n)); \ | ||
2527 | DO_OP(); \ | ||
2528 | JS_END_MACRO | ||
2529 | |||
2530 | # define BEGIN_CASE(OP) L_##OP: \ | ||
2531 | CHECK_RECORDER(); | ||
2532 | # define END_CASE(OP) DO_NEXT_OP(OP##_LENGTH); | ||
2533 | # define END_VARLEN_CASE DO_NEXT_OP(len); | ||
2534 | # define ADD_EMPTY_CASE(OP) BEGIN_CASE(OP) \ | ||
2535 | JS_ASSERT(js_CodeSpec[OP].length == 1); \ | ||
2536 | op = (JSOp) *++regs.pc; \ | ||
2537 | DO_OP(); | ||
2538 | |||
2539 | # define END_EMPTY_CASES | ||
2540 | |||
2541 | #else /* !JS_THREADED_INTERP */ | ||
2542 | |||
2543 | # define DO_OP() goto do_op | ||
2544 | # define DO_NEXT_OP(n) JS_BEGIN_MACRO \ | ||
2545 | JS_ASSERT((n) == len); \ | ||
2546 | goto advance_pc; \ | ||
2547 | JS_END_MACRO | ||
2548 | |||
2549 | # define BEGIN_CASE(OP) case OP: | ||
2550 | # define END_CASE(OP) END_CASE_LEN(OP##_LENGTH) | ||
2551 | # define END_CASE_LEN(n) END_CASE_LENX(n) | ||
2552 | # define END_CASE_LENX(n) END_CASE_LEN##n | ||
2553 | |||
2554 | /* | ||
2555 | * To share the code for all len == 1 cases we use the specialized label with | ||
2556 | * code that falls through to advance_pc: . | ||
2557 | */ | ||
2558 | # define END_CASE_LEN1 goto advance_pc_by_one; | ||
2559 | # define END_CASE_LEN2 len = 2; goto advance_pc; | ||
2560 | # define END_CASE_LEN3 len = 3; goto advance_pc; | ||
2561 | # define END_CASE_LEN4 len = 4; goto advance_pc; | ||
2562 | # define END_CASE_LEN5 len = 5; goto advance_pc; | ||
2563 | # define END_VARLEN_CASE goto advance_pc; | ||
2564 | # define ADD_EMPTY_CASE(OP) BEGIN_CASE(OP) | ||
2565 | # define END_EMPTY_CASES goto advance_pc_by_one; | ||
2566 | |||
2567 | #endif /* !JS_THREADED_INTERP */ | ||
2568 | |||
2569 | #ifdef JS_TRACER | ||
2570 | /* We had better not be entering the interpreter from JIT-compiled code. */ | ||
2571 | TraceRecorder *tr = NULL; | ||
2572 | if (JS_ON_TRACE(cx)) { | ||
2573 | tr = TRACE_RECORDER(cx); | ||
2574 | SET_TRACE_RECORDER(cx, NULL); | ||
2575 | JS_TRACE_MONITOR(cx).onTrace = JS_FALSE; | ||
2576 | } | ||
2577 | #endif | ||
2578 | |||
2579 | /* Check for too deep of a native thread stack. */ | ||
2580 | JS_CHECK_RECURSION(cx, return JS_FALSE); | ||
2581 | |||
2582 | rt = cx->runtime; | ||
2583 | |||
2584 | /* Set registerized frame pointer and derived script pointer. */ | ||
2585 | fp = cx->fp; | ||
2586 | script = fp->script; | ||
2587 | JS_ASSERT(script->length != 0); | ||
2588 | |||
2589 | /* Count of JS function calls that nest in this C js_Interpret frame. */ | ||
2590 | inlineCallCount = 0; | ||
2591 | |||
2592 | /* | ||
2593 | * Initialize the index segment register used by LOAD_ATOM and | ||
2594 | * GET_FULL_INDEX macros below. As a register we use a pointer based on | ||
2595 | * the atom map to turn frequently executed LOAD_ATOM into simple array | ||
2596 | * access. For less frequent object and regexp loads we have to recover | ||
2597 | * the segment from atoms pointer first. | ||
2598 | */ | ||
2599 | atoms = script->atomMap.vector; | ||
2600 | |||
2601 | #define LOAD_ATOM(PCOFF) \ | ||
2602 | JS_BEGIN_MACRO \ | ||
2603 | JS_ASSERT((size_t)(atoms - script->atomMap.vector) < \ | ||
2604 | (size_t)(script->atomMap.length - \ | ||
2605 | GET_INDEX(regs.pc + PCOFF))); \ | ||
2606 | atom = atoms[GET_INDEX(regs.pc + PCOFF)]; \ | ||
2607 | JS_END_MACRO | ||
2608 | |||
2609 | #define GET_FULL_INDEX(PCOFF) \ | ||
2610 | (atoms - script->atomMap.vector + GET_INDEX(regs.pc + PCOFF)) | ||
2611 | |||
2612 | #define LOAD_OBJECT(PCOFF) \ | ||
2613 | JS_GET_SCRIPT_OBJECT(script, GET_FULL_INDEX(PCOFF), obj) | ||
2614 | |||
2615 | #define LOAD_FUNCTION(PCOFF) \ | ||
2616 | JS_GET_SCRIPT_FUNCTION(script, GET_FULL_INDEX(PCOFF), fun) | ||
2617 | |||
2618 | #ifdef JS_TRACER | ||
2619 | |||
2620 | #define MONITOR_BRANCH(oldpc) \ | ||
2621 | JS_BEGIN_MACRO \ | ||
2622 | if (TRACING_ENABLED(cx)) { \ | ||
2623 | ENABLE_TRACER(js_MonitorLoopEdge(cx, oldpc, inlineCallCount)); \ | ||
2624 | fp = cx->fp; \ | ||
2625 | script = fp->script; \ | ||
2626 | atoms = script->atomMap.vector; \ | ||
2627 | currentVersion = (JSVersion) script->version; \ | ||
2628 | JS_ASSERT(fp->regs == ®s); \ | ||
2629 | } \ | ||
2630 | JS_END_MACRO | ||
2631 | |||
2632 | #else /* !JS_TRACER */ | ||
2633 | |||
2634 | #define MONITOR_BRANCH(oldpc) ((void) 0) | ||
2635 | |||
2636 | #endif /* !JS_TRACER */ | ||
2637 | |||
2638 | /* | ||
2639 | * Prepare to call a user-supplied branch handler, and abort the script | ||
2640 | * if it returns false. | ||
2641 | */ | ||
2642 | #define CHECK_BRANCH() \ | ||
2643 | JS_BEGIN_MACRO \ | ||
2644 | if ((cx->operationCount -= JSOW_SCRIPT_JUMP) <= 0) { \ | ||
2645 | if (!js_ResetOperationCount(cx)) \ | ||
2646 | goto error; \ | ||
2647 | } \ | ||
2648 | JS_END_MACRO | ||
2649 | |||
2650 | #define BRANCH(n) \ | ||
2651 | JS_BEGIN_MACRO \ | ||
2652 | regs.pc += n; \ | ||
2653 | if (n <= 0) { \ | ||
2654< |