Parent Directory
|
Revision Log
|
Patch
revision 459 by siliconforks, Tue Dec 9 03:37:47 2008 UTC | revision 460 by siliconforks, Sat Sep 26 23:15:22 2009 UTC | |
---|---|---|
# | Line 89 | Line 89 |
89 | /* | /* |
90 | * Asserts to verify assumptions behind pn_ macros. | * Asserts to verify assumptions behind pn_ macros. |
91 | */ | */ |
92 | JS_STATIC_ASSERT(offsetof(JSParseNode, pn_u.name.atom) == | #define pn_offsetof(m) offsetof(JSParseNode, m) |
93 | offsetof(JSParseNode, pn_u.apair.atom)); | |
94 | JS_STATIC_ASSERT(offsetof(JSParseNode, pn_u.name.slot) == | JS_STATIC_ASSERT(pn_offsetof(pn_link) == pn_offsetof(dn_uses)); |
95 | offsetof(JSParseNode, pn_u.lexical.slot)); | JS_STATIC_ASSERT(pn_offsetof(pn_u.name.atom) == pn_offsetof(pn_u.apair.atom)); |
96 | ||
97 | #undef pn_offsetof | |
98 | ||
99 | /* | /* |
100 | * JS parsers, from lowest to highest precedence. | * JS parsers, from lowest to highest precedence. |
# | Line 105 | Line 107 |
107 | JSParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc); | JSParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc); |
108 | ||
109 | typedef JSParseNode * | typedef JSParseNode * |
110 | JSVariablesParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, | |
111 | bool inLetHead); | |
112 | ||
113 | typedef JSParseNode * | |
114 | JSMemberParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, | JSMemberParser(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, |
115 | JSBool allowCallSyntax); | JSBool allowCallSyntax); |
116 | ||
# | Line 120 | Line 126 |
126 | static JSParser FunctionExpr; | static JSParser FunctionExpr; |
127 | static JSParser Statements; | static JSParser Statements; |
128 | static JSParser Statement; | static JSParser Statement; |
129 | static JSParser Variables; | static JSVariablesParser Variables; |
130 | static JSParser Expr; | static JSParser Expr; |
131 | static JSParser AssignExpr; | static JSParser AssignExpr; |
132 | static JSParser CondExpr; | static JSParser CondExpr; |
# | Line 157 | Line 163 |
163 | static uint32 recyclednodes = 0; | static uint32 recyclednodes = 0; |
164 | #endif | #endif |
165 | ||
166 | JSBool | void |
167 | js_InitParseContext(JSContext *cx, JSParseContext *pc, JSPrincipals *principals, | JSParseNode::become(JSParseNode *pn2) |
168 | JSStackFrame *callerFrame, | { |
169 | const jschar *base, size_t length, | JS_ASSERT(!pn_defn); |
170 | FILE *fp, const char *filename, uintN lineno) | JS_ASSERT(!pn2->pn_defn); |
171 | { | |
172 | JS_ASSERT_IF(callerFrame, callerFrame->script); | JS_ASSERT(!pn_used); |
173 | if (pn2->pn_used) { | |
174 | pc->tempPoolMark = JS_ARENA_MARK(&cx->tempPool); | JSParseNode **pnup = &pn2->pn_lexdef->dn_uses; |
175 | if (!js_InitTokenStream(cx, TS(pc), base, length, fp, filename, lineno)) { | while (*pnup != pn2) |
176 | JS_ARENA_RELEASE(&cx->tempPool, pc->tempPoolMark); | pnup = &(*pnup)->pn_link; |
177 | return JS_FALSE; | *pnup = this; |
178 | pn_link = pn2->pn_link; | |
179 | pn_used = true; | |
180 | pn2->pn_link = NULL; | |
181 | pn2->pn_used = false; | |
182 | } | |
183 | ||
184 | /* If this is a function node fix up the pn_funbox->node back-pointer. */ | |
185 | if (PN_TYPE(pn2) == TOK_FUNCTION && pn2->pn_arity == PN_FUNC) | |
186 | pn2->pn_funbox->node = this; | |
187 | ||
188 | pn_type = pn2->pn_type; | |
189 | pn_op = pn2->pn_op; | |
190 | pn_arity = pn2->pn_arity; | |
191 | pn_u = pn2->pn_u; | |
192 | pn2->clear(); | |
193 | } | |
194 | ||
195 | void | |
196 | JSParseNode::clear() | |
197 | { | |
198 | pn_type = TOK_EOF; | |
199 | pn_op = JSOP_NOP; | |
200 | pn_used = pn_defn = false; | |
201 | pn_arity = PN_NULLARY; | |
202 | } | |
203 | ||
204 | bool | |
205 | JSCompiler::init(const jschar *base, size_t length, | |
206 | FILE *fp, const char *filename, uintN lineno) | |
207 | { | |
208 | JSContext *cx = context; | |
209 | ||
210 | tempPoolMark = JS_ARENA_MARK(&cx->tempPool); | |
211 | if (!js_InitTokenStream(cx, TS(this), base, length, fp, filename, lineno)) { | |
212 | JS_ARENA_RELEASE(&cx->tempPool, tempPoolMark); | |
213 | return false; | |
214 | } | } |
if (principals) | ||
JSPRINCIPALS_HOLD(cx, principals); | ||
pc->principals = principals; | ||
pc->callerFrame = callerFrame; | ||
pc->nodeList = NULL; | ||
pc->traceListHead = NULL; | ||
215 | ||
216 | /* Root atoms and objects allocated for the parsed tree. */ | /* Root atoms and objects allocated for the parsed tree. */ |
217 | JS_KEEP_ATOMS(cx->runtime); | JS_KEEP_ATOMS(cx->runtime); |
218 | JS_PUSH_TEMP_ROOT_PARSE_CONTEXT(cx, pc, &pc->tempRoot); | JS_PUSH_TEMP_ROOT_COMPILER(cx, this, &tempRoot); |
219 | return JS_TRUE; | return true; |
220 | } | } |
221 | ||
222 | void | JSCompiler::~JSCompiler() |
js_FinishParseContext(JSContext *cx, JSParseContext *pc) | ||
223 | { | { |
224 | if (pc->principals) | JSContext *cx = context; |
225 | JSPRINCIPALS_DROP(cx, pc->principals); | |
226 | JS_ASSERT(pc->tempRoot.u.parseContext == pc); | if (principals) |
227 | JS_POP_TEMP_ROOT(cx, &pc->tempRoot); | JSPRINCIPALS_DROP(cx, principals); |
228 | JS_ASSERT(tempRoot.u.compiler == this); | |
229 | JS_POP_TEMP_ROOT(cx, &tempRoot); | |
230 | JS_UNKEEP_ATOMS(cx->runtime); | JS_UNKEEP_ATOMS(cx->runtime); |
231 | js_CloseTokenStream(cx, TS(pc)); | js_CloseTokenStream(cx, TS(this)); |
232 | JS_ARENA_RELEASE(&cx->tempPool, pc->tempPoolMark); | JS_ARENA_RELEASE(&cx->tempPool, tempPoolMark); |
233 | } | } |
234 | ||
235 | void | void |
236 | js_InitCompilePrincipals(JSContext *cx, JSParseContext *pc, | JSCompiler::setPrincipals(JSPrincipals *prin) |
JSPrincipals *principals) | ||
237 | { | { |
238 | JS_ASSERT(!pc->principals); | JS_ASSERT(!principals); |
239 | if (principals) | if (prin) |
240 | JSPRINCIPALS_HOLD(cx, principals); | JSPRINCIPALS_HOLD(context, prin); |
241 | pc->principals = principals; | principals = prin; |
242 | } | } |
243 | ||
244 | JSParsedObjectBox * | JSObjectBox * |
245 | js_NewParsedObjectBox(JSContext *cx, JSParseContext *pc, JSObject *obj) | JSCompiler::newObjectBox(JSObject *obj) |
246 | { | { |
247 | JSParsedObjectBox *pob; | JS_ASSERT(obj); |
248 | ||
249 | /* | /* |
250 | * We use JSContext.tempPool to allocate parsed objects and place them on | * We use JSContext.tempPool to allocate parsed objects and place them on |
# | Line 216 | Line 252 |
252 | * containing the entries must be alive until we are done with scanning, | * containing the entries must be alive until we are done with scanning, |
253 | * parsing and code generation for the whole script or top-level function. | * parsing and code generation for the whole script or top-level function. |
254 | */ | */ |
255 | JS_ASSERT(obj); | JSObjectBox *objbox; |
256 | JS_ARENA_ALLOCATE_TYPE(pob, JSParsedObjectBox, &cx->tempPool); | JS_ARENA_ALLOCATE_TYPE(objbox, JSObjectBox, &context->tempPool); |
257 | if (!pob) { | if (!objbox) { |
258 | js_ReportOutOfScriptQuota(cx); | js_ReportOutOfScriptQuota(context); |
259 | return NULL; | return NULL; |
260 | } | } |
261 | pob->traceLink = pc->traceListHead; | objbox->traceLink = traceListHead; |
262 | pob->emitLink = NULL; | traceListHead = objbox; |
263 | pob->object = obj; | objbox->emitLink = NULL; |
264 | pc->traceListHead = pob; | objbox->object = obj; |
265 | return pob; | return objbox; |
266 | } | } |
267 | ||
268 | JSFunctionBox * | |
269 | JSCompiler::newFunctionBox(JSObject *obj, JSParseNode *fn, JSTreeContext *tc) | |
270 | { | |
271 | JS_ASSERT(obj); | |
272 | JS_ASSERT(HAS_FUNCTION_CLASS(obj)); | |
273 | ||
274 | /* | |
275 | * We use JSContext.tempPool to allocate parsed objects and place them on | |
276 | * a list in JSTokenStream to ensure GC safety. Thus the tempPool arenas | |
277 | * containing the entries must be alive until we are done with scanning, | |
278 | * parsing and code generation for the whole script or top-level function. | |
279 | */ | |
280 | JSFunctionBox *funbox; | |
281 | JS_ARENA_ALLOCATE_TYPE(funbox, JSFunctionBox, &context->tempPool); | |
282 | if (!funbox) { | |
283 | js_ReportOutOfScriptQuota(context); | |
284 | return NULL; | |
285 | } | |
286 | funbox->traceLink = traceListHead; | |
287 | traceListHead = funbox; | |
288 | funbox->emitLink = NULL; | |
289 | funbox->object = obj; | |
290 | funbox->node = fn; | |
291 | funbox->siblings = tc->functionList; | |
292 | tc->functionList = funbox; | |
293 | ++tc->compiler->functionCount; | |
294 | funbox->kids = NULL; | |
295 | funbox->parent = tc->funbox; | |
296 | funbox->queued = false; | |
297 | funbox->inLoop = false; | |
298 | for (JSStmtInfo *stmt = tc->topStmt; stmt; stmt = stmt->down) { | |
299 | if (STMT_IS_LOOP(stmt)) { | |
300 | funbox->inLoop = true; | |
301 | break; | |
302 | } | |
303 | } | |
304 | funbox->level = tc->staticLevel; | |
305 | funbox->tcflags = TCF_IN_FUNCTION | (tc->flags & TCF_COMPILE_N_GO); | |
306 | return funbox; | |
307 | } | |
308 | ||
309 | void | void |
310 | js_TraceParseContext(JSTracer *trc, JSParseContext *pc) | JSCompiler::trace(JSTracer *trc) |
311 | { | { |
312 | JSParsedObjectBox *pob; | JSObjectBox *objbox; |
313 | ||
314 | JS_ASSERT(pc->tempRoot.u.parseContext == pc); | JS_ASSERT(tempRoot.u.compiler == this); |
315 | pob = pc->traceListHead; | objbox = traceListHead; |
316 | while (pob) { | while (objbox) { |
317 | JS_CALL_OBJECT_TRACER(trc, pob->object, "parser.object"); | JS_CALL_OBJECT_TRACER(trc, objbox->object, "parser.object"); |
318 | pob = pob->traceLink; | objbox = objbox->traceLink; |
319 | } | } |
320 | } | } |
321 | ||
322 | static void | |
323 | UnlinkFunctionBoxes(JSParseNode *pn, JSTreeContext *tc); | |
324 | ||
325 | static void | |
326 | UnlinkFunctionBox(JSParseNode *pn, JSTreeContext *tc) | |
327 | { | |
328 | JSFunctionBox *funbox = pn->pn_funbox; | |
329 | if (funbox) { | |
330 | JS_ASSERT(funbox->node == pn); | |
331 | funbox->node = NULL; | |
332 | ||
333 | JSFunctionBox **funboxp = &tc->functionList; | |
334 | while (*funboxp) { | |
335 | if (*funboxp == funbox) { | |
336 | *funboxp = funbox->siblings; | |
337 | break; | |
338 | } | |
339 | funboxp = &(*funboxp)->siblings; | |
340 | } | |
341 | ||
342 | uint16 oldflags = tc->flags; | |
343 | JSFunctionBox *oldlist = tc->functionList; | |
344 | ||
345 | tc->flags = (uint16) funbox->tcflags; | |
346 | tc->functionList = funbox->kids; | |
347 | UnlinkFunctionBoxes(pn->pn_body, tc); | |
348 | funbox->kids = tc->functionList; | |
349 | tc->flags = oldflags; | |
350 | tc->functionList = oldlist; | |
351 | ||
352 | // FIXME: use a funbox freelist (consolidate aleFreeList and nodeList). | |
353 | pn->pn_funbox = NULL; | |
354 | } | |
355 | } | |
356 | ||
357 | static void | |
358 | UnlinkFunctionBoxes(JSParseNode *pn, JSTreeContext *tc) | |
359 | { | |
360 | if (pn) { | |
361 | switch (pn->pn_arity) { | |
362 | case PN_NULLARY: | |
363 | return; | |
364 | case PN_UNARY: | |
365 | UnlinkFunctionBoxes(pn->pn_kid, tc); | |
366 | return; | |
367 | case PN_BINARY: | |
368 | UnlinkFunctionBoxes(pn->pn_left, tc); | |
369 | UnlinkFunctionBoxes(pn->pn_right, tc); | |
370 | return; | |
371 | case PN_TERNARY: | |
372 | UnlinkFunctionBoxes(pn->pn_kid1, tc); | |
373 | UnlinkFunctionBoxes(pn->pn_kid2, tc); | |
374 | UnlinkFunctionBoxes(pn->pn_kid3, tc); | |
375 | return; | |
376 | case PN_LIST: | |
377 | for (JSParseNode *pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) | |
378 | UnlinkFunctionBoxes(pn2, tc); | |
379 | return; | |
380 | case PN_FUNC: | |
381 | UnlinkFunctionBox(pn, tc); | |
382 | return; | |
383 | case PN_NAME: | |
384 | UnlinkFunctionBoxes(pn->maybeExpr(), tc); | |
385 | return; | |
386 | case PN_NAMESET: | |
387 | UnlinkFunctionBoxes(pn->pn_tree, tc); | |
388 | } | |
389 | } | |
390 | } | |
391 | ||
392 | static void | |
393 | RecycleFuncNameKids(JSParseNode *pn, JSTreeContext *tc); | |
394 | ||
395 | static JSParseNode * | static JSParseNode * |
396 | RecycleTree(JSParseNode *pn, JSTreeContext *tc) | RecycleTree(JSParseNode *pn, JSTreeContext *tc) |
397 | { | { |
398 | JSParseNode *next; | JSParseNode *next, **head; |
399 | ||
400 | if (!pn) | if (!pn) |
401 | return NULL; | return NULL; |
402 | ||
403 | /* Catch back-to-back dup recycles. */ | /* Catch back-to-back dup recycles. */ |
404 | JS_ASSERT(pn != tc->parseContext->nodeList); | JS_ASSERT(pn != tc->compiler->nodeList); |
405 | next = pn->pn_next; | next = pn->pn_next; |
406 | pn->pn_next = tc->parseContext->nodeList; | if (pn->pn_used || pn->pn_defn) { |
407 | tc->parseContext->nodeList = pn; | /* |
408 | * JSAtomLists own definition nodes along with their used-node chains. | |
409 | * Defer recycling such nodes until we unwind to top level to avoid | |
410 | * linkage overhead or (alternatively) unlinking runtime complexity. | |
411 | * Yes, this means dead code can contribute to static analysis results! | |
412 | * | |
413 | * Do recycle kids here, since they are no longer needed. | |
414 | */ | |
415 | pn->pn_next = NULL; | |
416 | RecycleFuncNameKids(pn, tc); | |
417 | } else { | |
418 | UnlinkFunctionBoxes(pn, tc); | |
419 | head = &tc->compiler->nodeList; | |
420 | pn->pn_next = *head; | |
421 | *head = pn; | |
422 | #ifdef METER_PARSENODES | #ifdef METER_PARSENODES |
423 | recyclednodes++; | recyclednodes++; |
424 | #endif | #endif |
425 | } | |
426 | return next; | return next; |
427 | } | } |
428 | ||
429 | static void | |
430 | RecycleFuncNameKids(JSParseNode *pn, JSTreeContext *tc) | |
431 | { | |
432 | switch (pn->pn_arity) { | |
433 | case PN_FUNC: | |
434 | UnlinkFunctionBox(pn, tc); | |
435 | /* FALL THROUGH */ | |
436 | ||
437 | case PN_NAME: | |
438 | /* | |
439 | * Only a definition node might have a non-null strong pn_expr link | |
440 | * to recycle, but we test !pn_used to handle PN_FUNC fall through. | |
441 | * Every node with the pn_used flag set has a non-null pn_lexdef | |
442 | * weak reference to its definition node. | |
443 | */ | |
444 | if (!pn->pn_used && pn->pn_expr) { | |
445 | RecycleTree(pn->pn_expr, tc); | |
446 | pn->pn_expr = NULL; | |
447 | } | |
448 | break; | |
449 | ||
450 | default: | |
451 | JS_ASSERT(PN_TYPE(pn) == TOK_FUNCTION); | |
452 | } | |
453 | } | |
454 | ||
455 | static JSParseNode * | static JSParseNode * |
456 | NewOrRecycledNode(JSContext *cx, JSTreeContext *tc) | NewOrRecycledNode(JSTreeContext *tc) |
457 | { | { |
458 | JSParseNode *pn; | JSParseNode *pn, *pn2; |
459 | ||
460 | pn = tc->parseContext->nodeList; | pn = tc->compiler->nodeList; |
461 | if (!pn) { | if (!pn) { |
462 | JSContext *cx = tc->compiler->context; | |
463 | ||
464 | JS_ARENA_ALLOCATE_TYPE(pn, JSParseNode, &cx->tempPool); | JS_ARENA_ALLOCATE_TYPE(pn, JSParseNode, &cx->tempPool); |
465 | if (!pn) | if (!pn) |
466 | js_ReportOutOfScriptQuota(cx); | js_ReportOutOfScriptQuota(cx); |
467 | } else { | } else { |
468 | tc->parseContext->nodeList = pn->pn_next; | tc->compiler->nodeList = pn->pn_next; |
469 | ||
470 | /* Recycle immediate descendents only, to save work and working set. */ | /* Recycle immediate descendents only, to save work and working set. */ |
471 | switch (pn->pn_arity) { | switch (pn->pn_arity) { |
# | Line 281 | Line 473 |
473 | RecycleTree(pn->pn_body, tc); | RecycleTree(pn->pn_body, tc); |
474 | break; | break; |
475 | case PN_LIST: | case PN_LIST: |
476 | if (pn->pn_head) { | pn2 = pn->pn_head; |
477 | /* XXX check for dup recycles in the list */ | if (pn2) { |
478 | *pn->pn_tail = tc->parseContext->nodeList; | while (pn2 && !pn2->pn_used && !pn2->pn_defn) |
479 | tc->parseContext->nodeList = pn->pn_head; | pn2 = pn2->pn_next; |
480 | if (pn2) { | |
481 | pn2 = pn->pn_head; | |
482 | do { | |
483 | pn2 = RecycleTree(pn2, tc); | |
484 | } while (pn2); | |
485 | } else { | |
486 | *pn->pn_tail = tc->compiler->nodeList; | |
487 | tc->compiler->nodeList = pn->pn_head; | |
488 | #ifdef METER_PARSENODES | #ifdef METER_PARSENODES |
489 | recyclednodes += pn->pn_count; | recyclednodes += pn->pn_count; |
490 | #endif | #endif |
491 | break; | |
492 | } | |
493 | } | } |
494 | break; | break; |
495 | case PN_TERNARY: | case PN_TERNARY: |
# | Line 304 | Line 506 |
506 | RecycleTree(pn->pn_kid, tc); | RecycleTree(pn->pn_kid, tc); |
507 | break; | break; |
508 | case PN_NAME: | case PN_NAME: |
509 | RecycleTree(pn->pn_expr, tc); | if (!pn->pn_used) |
510 | RecycleTree(pn->pn_expr, tc); | |
511 | break; | break; |
512 | case PN_NULLARY: | case PN_NULLARY: |
513 | break; | break; |
# | Line 316 | Line 519 |
519 | if (parsenodes - recyclednodes > maxparsenodes) | if (parsenodes - recyclednodes > maxparsenodes) |
520 | maxparsenodes = parsenodes - recyclednodes; | maxparsenodes = parsenodes - recyclednodes; |
521 | #endif | #endif |
522 | pn->pn_used = pn->pn_defn = false; | |
523 | memset(&pn->pn_u, 0, sizeof pn->pn_u); | memset(&pn->pn_u, 0, sizeof pn->pn_u); |
524 | pn->pn_next = NULL; | pn->pn_next = NULL; |
525 | } | } |
526 | return pn; | return pn; |
527 | } | } |
528 | ||
529 | static inline void | |
530 | InitParseNode(JSParseNode *pn, JSTokenType type, JSOp op, JSParseNodeArity arity) | |
531 | { | |
532 | pn->pn_type = type; | |
533 | pn->pn_op = op; | |
534 | pn->pn_arity = arity; | |
535 | JS_ASSERT(!pn->pn_used); | |
536 | JS_ASSERT(!pn->pn_defn); | |
537 | pn->pn_next = pn->pn_link = NULL; | |
538 | } | |
539 | ||
540 | /* | /* |
541 | * Allocate a JSParseNode from cx's temporary arena. | * Allocate a JSParseNode from tc's node freelist or, failing that, from cx's |
542 | * temporary arena. | |
543 | */ | */ |
544 | static JSParseNode * | static JSParseNode * |
545 | NewParseNode(JSContext *cx, JSTokenStream *ts, JSParseNodeArity arity, | NewParseNode(JSParseNodeArity arity, JSTreeContext *tc) |
JSTreeContext *tc) | ||
546 | { | { |
547 | JSParseNode *pn; | JSParseNode *pn; |
548 | JSToken *tp; | JSToken *tp; |
549 | ||
550 | pn = NewOrRecycledNode(cx, tc); | pn = NewOrRecycledNode(tc); |
551 | if (!pn) | if (!pn) |
552 | return NULL; | return NULL; |
553 | tp = &CURRENT_TOKEN(ts); | tp = &CURRENT_TOKEN(&tc->compiler->tokenStream); |
554 | pn->pn_type = tp->type; | InitParseNode(pn, tp->type, JSOP_NOP, arity); |
555 | pn->pn_pos = tp->pos; | pn->pn_pos = tp->pos; |
556 | pn->pn_op = JSOP_NOP; | return pn; |
557 | pn->pn_arity = arity; | } |
558 | ||
559 | static inline void | |
560 | InitNameNodeCommon(JSParseNode *pn, JSTreeContext *tc) | |
561 | { | |
562 | pn->pn_expr = NULL; | |
563 | pn->pn_cookie = FREE_UPVAR_COOKIE; | |
564 | pn->pn_dflags = tc->atTopLevel() ? PND_TOPLEVEL : 0; | |
565 | if (!tc->topStmt || tc->topStmt->type == STMT_BLOCK) | |
566 | pn->pn_dflags |= PND_BLOCKCHILD; | |
567 | pn->pn_blockid = tc->blockid(); | |
568 | } | |
569 | ||
570 | static JSParseNode * | |
571 | NewNameNode(JSContext *cx, JSTokenStream *ts, JSAtom *atom, JSTreeContext *tc) | |
572 | { | |
573 | JSParseNode *pn; | |
574 | ||
575 | pn = NewParseNode(PN_NAME, tc); | |
576 | if (pn) { | |
577 | pn->pn_atom = atom; | |
578 | InitNameNodeCommon(pn, tc); | |
579 | } | |
580 | return pn; | return pn; |
581 | } | } |
582 | ||
583 | static JSParseNode * | static JSParseNode * |
584 | NewBinary(JSContext *cx, JSTokenType tt, | NewBinary(JSTokenType tt, JSOp op, JSParseNode *left, JSParseNode *right, |
JSOp op, JSParseNode *left, JSParseNode *right, | ||
585 | JSTreeContext *tc) | JSTreeContext *tc) |
586 | { | { |
587 | JSParseNode *pn, *pn1, *pn2; | JSParseNode *pn, *pn1, *pn2; |
# | Line 357 | Line 593 |
593 | * Flatten a left-associative (left-heavy) tree of a given operator into | * Flatten a left-associative (left-heavy) tree of a given operator into |
594 | * a list, to reduce js_FoldConstants and js_EmitTree recursion. | * a list, to reduce js_FoldConstants and js_EmitTree recursion. |
595 | */ | */ |
596 | if (left->pn_type == tt && | if (PN_TYPE(left) == tt && |
597 | left->pn_op == op && | PN_OP(left) == op && |
598 | (js_CodeSpec[op].format & JOF_LEFTASSOC)) { | (js_CodeSpec[op].format & JOF_LEFTASSOC)) { |
599 | if (left->pn_arity != PN_LIST) { | if (left->pn_arity != PN_LIST) { |
600 | pn1 = left->pn_left, pn2 = left->pn_right; | pn1 = left->pn_left, pn2 = left->pn_right; |
601 | left->pn_arity = PN_LIST; | left->pn_arity = PN_LIST; |
602 | PN_INIT_LIST_1(left, pn1); | left->initList(pn1); |
603 | PN_APPEND(left, pn2); | left->append(pn2); |
604 | if (tt == TOK_PLUS) { | if (tt == TOK_PLUS) { |
605 | if (pn1->pn_type == TOK_STRING) | if (pn1->pn_type == TOK_STRING) |
606 | left->pn_extra |= PNX_STRCAT; | left->pn_xflags |= PNX_STRCAT; |
607 | else if (pn1->pn_type != TOK_NUMBER) | else if (pn1->pn_type != TOK_NUMBER) |
608 | left->pn_extra |= PNX_CANTFOLD; | left->pn_xflags |= PNX_CANTFOLD; |
609 | if (pn2->pn_type == TOK_STRING) | if (pn2->pn_type == TOK_STRING) |
610 | left->pn_extra |= PNX_STRCAT; | left->pn_xflags |= PNX_STRCAT; |
611 | else if (pn2->pn_type != TOK_NUMBER) | else if (pn2->pn_type != TOK_NUMBER) |
612 | left->pn_extra |= PNX_CANTFOLD; | left->pn_xflags |= PNX_CANTFOLD; |
613 | } | } |
614 | } | } |
615 | PN_APPEND(left, right); | left->append(right); |
616 | left->pn_pos.end = right->pn_pos.end; | left->pn_pos.end = right->pn_pos.end; |
617 | if (tt == TOK_PLUS) { | if (tt == TOK_PLUS) { |
618 | if (right->pn_type == TOK_STRING) | if (right->pn_type == TOK_STRING) |
619 | left->pn_extra |= PNX_STRCAT; | left->pn_xflags |= PNX_STRCAT; |
620 | else if (right->pn_type != TOK_NUMBER) | else if (right->pn_type != TOK_NUMBER) |
621 | left->pn_extra |= PNX_CANTFOLD; | left->pn_xflags |= PNX_CANTFOLD; |
622 | } | } |
623 | return left; | return left; |
624 | } | } |
# | Line 403 | Line 639 |
639 | return left; | return left; |
640 | } | } |
641 | ||
642 | pn = NewOrRecycledNode(cx, tc); | pn = NewOrRecycledNode(tc); |
643 | if (!pn) | if (!pn) |
644 | return NULL; | return NULL; |
645 | pn->pn_type = tt; | InitParseNode(pn, tt, op, PN_BINARY); |
646 | pn->pn_pos.begin = left->pn_pos.begin; | pn->pn_pos.begin = left->pn_pos.begin; |
647 | pn->pn_pos.end = right->pn_pos.end; | pn->pn_pos.end = right->pn_pos.end; |
pn->pn_op = op; | ||
pn->pn_arity = PN_BINARY; | ||
648 | pn->pn_left = left; | pn->pn_left = left; |
649 | pn->pn_right = right; | pn->pn_right = right; |
650 | return pn; | return pn; |
# | Line 460 | Line 694 |
694 | } | } |
695 | #endif | #endif |
696 | ||
697 | static bool | |
698 | GenerateBlockId(JSTreeContext *tc, uint32& blockid) | |
699 | { | |
700 | if (tc->blockidGen == JS_BIT(20)) { | |
701 | JS_ReportErrorNumber(tc->compiler->context, js_GetErrorMessage, NULL, | |
702 | JSMSG_NEED_DIET, "program"); | |
703 | return false; | |
704 | } | |
705 | blockid = tc->blockidGen++; | |
706 | return true; | |
707 | } | |
708 | ||
709 | static bool | |
710 | GenerateBlockIdForStmtNode(JSParseNode *pn, JSTreeContext *tc) | |
711 | { | |
712 | JS_ASSERT(tc->topStmt); | |
713 | JS_ASSERT(STMT_MAYBE_SCOPE(tc->topStmt)); | |
714 | JS_ASSERT(pn->pn_type == TOK_LC || pn->pn_type == TOK_LEXICALSCOPE); | |
715 | if (!GenerateBlockId(tc, tc->topStmt->blockid)) | |
716 | return false; | |
717 | pn->pn_blockid = tc->topStmt->blockid; | |
718 | return true; | |
719 | } | |
720 | ||
721 | /* | /* |
722 | * Parse a top-level JS script. | * Parse a top-level JS script. |
723 | */ | */ |
724 | JSParseNode * | JSParseNode * |
725 | js_ParseScript(JSContext *cx, JSObject *chain, JSParseContext *pc) | JSCompiler::parse(JSObject *chain) |
726 | { | { |
JSTreeContext tc; | ||
JSParseNode *pn; | ||
727 | /* | /* |
728 | * Protect atoms from being collected by a GC activation, which might | * Protect atoms from being collected by a GC activation, which might |
729 | * - nest on this thread due to out of memory (the so-called "last ditch" | * - nest on this thread due to out of memory (the so-called "last ditch" |
# | Line 477 | Line 732 |
732 | * an object lock before it finishes generating bytecode into a script | * an object lock before it finishes generating bytecode into a script |
733 | * protected from the GC by a root or a stack frame reference. | * protected from the GC by a root or a stack frame reference. |
734 | */ | */ |
735 | TREE_CONTEXT_INIT(&tc, pc); | JSTreeContext tc(this); |
736 | tc.u.scopeChain = chain; | tc.scopeChain = chain; |
737 | pn = Statements(cx, TS(pc), &tc); | if (!GenerateBlockId(&tc, tc.bodyid)) |
738 | return NULL; | |
739 | ||
740 | JSParseNode *pn = Statements(context, TS(this), &tc); | |
741 | if (pn) { | if (pn) { |
742 | if (!js_MatchToken(cx, TS(pc), TOK_EOF)) { | if (!js_MatchToken(context, TS(this), TOK_EOF)) { |
743 | js_ReportCompileErrorNumber(cx, TS(pc), NULL, JSREPORT_ERROR, | js_ReportCompileErrorNumber(context, TS(this), NULL, JSREPORT_ERROR, |
744 | JSMSG_SYNTAX_ERROR); | JSMSG_SYNTAX_ERROR); |
745 | pn = NULL; | pn = NULL; |
746 | } else { | } else { |
747 | pn->pn_type = TOK_LC; | if (!js_FoldConstants(context, pn, &tc)) |
if (!js_FoldConstants(cx, pn, &tc)) | ||
748 | pn = NULL; | pn = NULL; |
749 | } | } |
750 | } | } |
TREE_CONTEXT_FINISH(cx, &tc); | ||
751 | return pn; | return pn; |
752 | } | } |
753 | ||
754 | JS_STATIC_ASSERT(FREE_STATIC_LEVEL == JS_BITMASK(JSFB_LEVEL_BITS)); | |
755 | ||
756 | static inline bool | |
757 | SetStaticLevel(JSTreeContext *tc, uintN staticLevel) | |
758 | { | |
759 | /* | |
760 | * Reserve FREE_STATIC_LEVEL (0xffff) in order to reserve FREE_UPVAR_COOKIE | |
761 | * (0xffffffff) and other cookies with that level. | |
762 | * | |
763 | * This is a lot simpler than error-checking every MAKE_UPVAR_COOKIE, and | |
764 | * practically speaking it leaves more than enough room for upvars. In fact | |
765 | * we might want to split cookie fields giving fewer bits for skip and more | |
766 | * for slot, but only based on evidence. | |
767 | */ | |
768 | if (staticLevel >= FREE_STATIC_LEVEL) { | |
769 | JS_ReportErrorNumber(tc->compiler->context, js_GetErrorMessage, NULL, | |
770 | JSMSG_TOO_DEEP, js_function_str); | |
771 | return false; | |
772 | } | |
773 | tc->staticLevel = staticLevel; | |
774 | return true; | |
775 | } | |
776 | ||
777 | /* | /* |
778 | * Compile a top-level script. | * Compile a top-level script. |
779 | */ | */ |
780 | extern JSScript * | JSScript * |
781 | js_CompileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, | JSCompiler::compileScript(JSContext *cx, JSObject *scopeChain, JSStackFrame *callerFrame, |
782 | JSPrincipals *principals, uint32 tcflags, | JSPrincipals *principals, uint32 tcflags, |
783 | const jschar *chars, size_t length, | const jschar *chars, size_t length, |
784 | FILE *file, const char *filename, uintN lineno) | FILE *file, const char *filename, uintN lineno, |
785 | JSString *source /* = NULL */) | |
786 | { | { |
787 | JSParseContext pc; | JSCompiler jsc(cx, principals, callerFrame); |
788 | JSArenaPool codePool, notePool; | JSArenaPool codePool, notePool; |
JSCodeGenerator cg; | ||
789 | JSTokenType tt; | JSTokenType tt; |
790 | JSParseNode *pn; | JSParseNode *pn; |
791 | uint32 scriptGlobals; | uint32 scriptGlobals; |
# | Line 517 | Line 795 |
795 | #endif | #endif |
796 | ||
797 | JS_ASSERT(!(tcflags & ~(TCF_COMPILE_N_GO | TCF_NO_SCRIPT_RVAL | | JS_ASSERT(!(tcflags & ~(TCF_COMPILE_N_GO | TCF_NO_SCRIPT_RVAL | |
798 | TCF_STATIC_DEPTH_MASK))); | TCF_STATIC_LEVEL_MASK))); |
799 | ||
800 | /* | /* |
801 | * The scripted callerFrame can only be given for compile-and-go scripts | * The scripted callerFrame can only be given for compile-and-go scripts |
802 | * and non-zero static depth requires callerFrame. | * and non-zero static level requires callerFrame. |
803 | */ | */ |
804 | JS_ASSERT_IF(callerFrame, tcflags & TCF_COMPILE_N_GO); | JS_ASSERT_IF(callerFrame, tcflags & TCF_COMPILE_N_GO); |
805 | JS_ASSERT_IF(TCF_GET_STATIC_DEPTH(tcflags) != 0, callerFrame); | JS_ASSERT_IF(TCF_GET_STATIC_LEVEL(tcflags) != 0, callerFrame); |
806 | ||
807 | if (!js_InitParseContext(cx, &pc, principals, callerFrame, chars, length, | if (!jsc.init(chars, length, file, filename, lineno)) |
file, filename, lineno)) { | ||
808 | return NULL; | return NULL; |
} | ||
809 | ||
810 | JS_INIT_ARENA_POOL(&codePool, "code", 1024, sizeof(jsbytecode), | JS_INIT_ARENA_POOL(&codePool, "code", 1024, sizeof(jsbytecode), |
811 | &cx->scriptStackQuota); | &cx->scriptStackQuota); |
812 | JS_INIT_ARENA_POOL(¬ePool, "note", 1024, sizeof(jssrcnote), | JS_INIT_ARENA_POOL(¬ePool, "note", 1024, sizeof(jssrcnote), |
813 | &cx->scriptStackQuota); | &cx->scriptStackQuota); |
814 | js_InitCodeGenerator(cx, &cg, &pc, &codePool, ¬ePool, | |
815 | pc.tokenStream.lineno); | JSCodeGenerator cg(&jsc, &codePool, ¬ePool, jsc.tokenStream.lineno); |
816 | ||
817 | MUST_FLOW_THROUGH("out"); | MUST_FLOW_THROUGH("out"); |
cg.treeContext.flags |= (uint16) tcflags; | ||
cg.treeContext.u.scopeChain = scopeChain; | ||
cg.staticDepth = TCF_GET_STATIC_DEPTH(tcflags); | ||
818 | ||
819 | if ((tcflags & TCF_COMPILE_N_GO) && callerFrame && callerFrame->fun) { | /* Null script early in case of error, to reduce our code footprint. */ |
820 | /* | script = NULL; |
821 | * An eval script in a caller frame needs to have its enclosing function | |
822 | * captured in case it uses an upvar reference, and someone wishes to | cg.flags |= uint16(tcflags); |
823 | * decompile it while running. | cg.scopeChain = scopeChain; |
824 | */ | if (!SetStaticLevel(&cg, TCF_GET_STATIC_LEVEL(tcflags))) |
825 | JSParsedObjectBox *pob = js_NewParsedObjectBox(cx, &pc, callerFrame->callee); | goto out; |
826 | pob->emitLink = cg.objectList.lastPob; | |
827 | cg.objectList.lastPob = pob; | /* |
828 | cg.objectList.length++; | * If funbox is non-null after we create the new script, callerFrame->fun |
829 | * was saved in the 0th object table entry. | |
830 | */ | |
831 | JSObjectBox *funbox; | |
832 | funbox = NULL; | |
833 | ||
834 | if (tcflags & TCF_COMPILE_N_GO) { | |
835 | if (source) { | |
836 | /* | |
837 | * Save eval program source in script->atomMap.vector[0] for the | |
838 | * eval cache (see obj_eval in jsobj.cpp). | |
839 | */ | |
840 | JSAtom *atom = js_AtomizeString(cx, source, 0); | |
841 | if (!atom || !cg.atomList.add(&jsc, atom)) | |
842 | goto out; | |
843 | } | |
844 | ||
845 | if (callerFrame && callerFrame->fun) { | |
846 | /* | |
847 | * An eval script in a caller frame needs to have its enclosing | |
848 | * function captured in case it refers to an upvar, and someone | |
849 | * wishes to decompile it while it's running. | |
850 | */ | |
851 | funbox = jsc.newObjectBox(FUN_OBJECT(callerFrame->fun)); | |
852 | if (!funbox) | |
853 | goto out; | |
854 | funbox->emitLink = cg.objectList.lastbox; | |
855 | cg.objectList.lastbox = funbox; | |
856 | cg.objectList.length++; | |
857 | } | |
858 | } | } |
859 | ||
860 | /* Inline Statements() to emit as we go to save space. */ | /* |
861 | * Inline Statements to emit as we go to save AST space. We must generate | |
862 | * our script-body blockid since we aren't calling Statements. | |
863 | */ | |
864 | uint32 bodyid; | |
865 | if (!GenerateBlockId(&cg, bodyid)) | |
866 | goto out; | |
867 | cg.bodyid = bodyid; | |
868 | ||
869 | #if JS_HAS_XML_SUPPORT | |
870 | pn = NULL; | |
871 | bool onlyXML; | |
872 | onlyXML = true; | |
873 | #endif | |
874 | ||
875 | for (;;) { | for (;;) { |
876 | pc.tokenStream.flags |= TSF_OPERAND; | jsc.tokenStream.flags |= TSF_OPERAND; |
877 | tt = js_PeekToken(cx, &pc.tokenStream); | tt = js_PeekToken(cx, &jsc.tokenStream); |
878 | pc.tokenStream.flags &= ~TSF_OPERAND; | jsc.tokenStream.flags &= ~TSF_OPERAND; |
879 | if (tt <= TOK_EOF) { | if (tt <= TOK_EOF) { |
880 | if (tt == TOK_EOF) | if (tt == TOK_EOF) |
881 | break; | break; |
882 | JS_ASSERT(tt == TOK_ERROR); | JS_ASSERT(tt == TOK_ERROR); |
script = NULL; | ||
883 | goto out; | goto out; |
884 | } | } |
885 | ||
886 | pn = Statement(cx, &pc.tokenStream, &cg.treeContext); | pn = Statement(cx, &jsc.tokenStream, &cg); |
887 | if (!pn) { | if (!pn) |
888 | script = NULL; | goto out; |
889 | JS_ASSERT(!cg.blockNode); | |
890 | ||
891 | if (!js_FoldConstants(cx, pn, &cg)) | |
892 | goto out; | goto out; |
893 | ||
894 | if (cg.functionList) { | |
895 | if (!jsc.analyzeFunctions(cg.functionList, cg.flags)) | |
896 | goto out; | |
897 | cg.functionList = NULL; | |
898 | } | } |
JS_ASSERT(!cg.treeContext.blockNode); | ||
899 | ||
900 | if (!js_FoldConstants(cx, pn, &cg.treeContext) || | if (!js_EmitTree(cx, &cg, pn)) |
!js_EmitTree(cx, &cg, pn)) { | ||
script = NULL; | ||
901 | goto out; | goto out; |
902 | #if JS_HAS_XML_SUPPORT | |
903 | if (PN_TYPE(pn) != TOK_SEMI || | |
904 | !pn->pn_kid || | |
905 | !TREE_TYPE_IS_XML(PN_TYPE(pn->pn_kid))) { | |
906 | onlyXML = false; | |
907 | } | } |
908 | RecycleTree(pn, &cg.treeContext); | #endif |
909 | RecycleTree(pn, &cg); | |
910 | } | } |
911 | ||
912 | #if JS_HAS_XML_SUPPORT | |
913 | /* | /* |
914 | * Global variables and regexps shares the index space with locals. Due to | * Prevent XML data theft via <script src="http://victim.com/foo.xml">. |
915 | * For background, see: | |
916 | * | |
917 | * https://bugzilla.mozilla.org/show_bug.cgi?id=336551 | |
918 | */ | |
919 | if (pn && onlyXML && (tcflags & TCF_NO_SCRIPT_RVAL)) { | |
920 | js_ReportCompileErrorNumber(cx, &jsc.tokenStream, NULL, JSREPORT_ERROR, | |
921 | JSMSG_XML_WHOLE_PROGRAM); | |
922 | goto out; | |
923 | } | |
924 | #endif | |
925 | ||
926 | /* | |
927 | * Global variables and regexps share the index space with locals. Due to | |
928 | * incremental code generation we need to patch the bytecode to adjust the | * incremental code generation we need to patch the bytecode to adjust the |
929 | * local references to skip the globals. | * local references to skip the globals. |
930 | */ | */ |
931 | scriptGlobals = cg.treeContext.ngvars + cg.regexpList.length; | scriptGlobals = cg.ngvars + cg.regexpList.length; |
932 | if (scriptGlobals != 0) { | if (scriptGlobals != 0) { |
933 | jsbytecode *code, *end; | jsbytecode *code, *end; |
934 | JSOp op; | JSOp op; |
# | Line 635 | Line 975 |
975 | * Nowadays the threaded interpreter needs a stop instruction, so we | * Nowadays the threaded interpreter needs a stop instruction, so we |
976 | * do have to emit that here. | * do have to emit that here. |
977 | */ | */ |
978 | if (js_Emit1(cx, &cg, JSOP_STOP) < 0) { | if (js_Emit1(cx, &cg, JSOP_STOP) < 0) |
script = NULL; | ||
979 | goto out; | goto out; |
} | ||
980 | #ifdef METER_PARSENODES | #ifdef METER_PARSENODES |
981 | printf("Code-gen growth: %d (%u bytecodes, %u srcnotes)\n", | printf("Code-gen growth: %d (%u bytecodes, %u srcnotes)\n", |
982 | (char *)sbrk(0) - (char *)before, CG_OFFSET(cg), cg->noteCount); | (char *)sbrk(0) - (char *)before, CG_OFFSET(&cg), cg.noteCount); |
983 | #endif | #endif |
984 | #ifdef JS_ARENAMETER | #ifdef JS_ARENAMETER |
985 | JS_DumpArenaStats(stdout); | JS_DumpArenaStats(stdout); |
986 | #endif | #endif |
987 | script = js_NewScriptFromCG(cx, &cg); | script = js_NewScriptFromCG(cx, &cg); |
988 | if (script && funbox) | |
989 | script->flags |= JSSF_SAVED_CALLER_FUN; | |
990 | ||
991 | #ifdef JS_SCOPE_DEPTH_METER | #ifdef JS_SCOPE_DEPTH_METER |
992 | if (script) { | if (script) { |
# | Line 659 | Line 999 |
999 | #endif | #endif |
1000 | ||
1001 | out: | out: |
js_FinishCodeGenerator(cx, &cg); | ||
1002 | JS_FinishArenaPool(&codePool); | JS_FinishArenaPool(&codePool); |
1003 | JS_FinishArenaPool(¬ePool); | JS_FinishArenaPool(¬ePool); |
js_FinishParseContext(cx, &pc); | ||
1004 | return script; | return script; |
1005 | ||
1006 | too_many_slots: | too_many_slots: |
1007 | js_ReportCompileErrorNumber(cx, &pc.tokenStream, NULL, | js_ReportCompileErrorNumber(cx, &jsc.tokenStream, NULL, |
1008 | JSREPORT_ERROR, JSMSG_TOO_MANY_LOCALS); | JSREPORT_ERROR, JSMSG_TOO_MANY_LOCALS); |
1009 | script = NULL; | script = NULL; |
1010 | goto out; | goto out; |
# | Line 692 | Line 1030 |
1030 | case TOK_LC: | case TOK_LC: |
1031 | if (!pn->pn_head) | if (!pn->pn_head) |
1032 | return ENDS_IN_OTHER; | return ENDS_IN_OTHER; |
1033 | return HasFinalReturn(PN_LAST(pn)); | return HasFinalReturn(pn->last()); |
1034 | ||
1035 | case TOK_IF: | case TOK_IF: |
1036 | if (!pn->pn_kid3) | if (!pn->pn_kid3) |
# | Line 733 | Line 1071 |
1071 | hasDefault = ENDS_IN_OTHER; | hasDefault = ENDS_IN_OTHER; |
1072 | pn2 = pn->pn_right; | pn2 = pn->pn_right; |
1073 | if (pn2->pn_type == TOK_LEXICALSCOPE) | if (pn2->pn_type == TOK_LEXICALSCOPE) |
1074 | pn2 = pn2->pn_expr; | pn2 = pn2->expr(); |
1075 | for (pn2 = pn2->pn_head; rv && pn2; pn2 = pn2->pn_next) { | for (pn2 = pn2->pn_head; rv && pn2; pn2 = pn2->pn_next) { |
1076 | if (pn2->pn_type == TOK_DEFAULT) | if (pn2->pn_type == TOK_DEFAULT) |
1077 | hasDefault = ENDS_IN_RETURN; | hasDefault = ENDS_IN_RETURN; |
1078 | pn3 = pn2->pn_right; | pn3 = pn2->pn_right; |
1079 | JS_ASSERT(pn3->pn_type == TOK_LC); | JS_ASSERT(pn3->pn_type == TOK_LC); |
1080 | if (pn3->pn_head) { | if (pn3->pn_head) { |
1081 | rv2 = HasFinalReturn(PN_LAST(pn3)); | rv2 = HasFinalReturn(pn3->last()); |
1082 | if (rv2 == ENDS_IN_OTHER && pn2->pn_next) | if (rv2 == ENDS_IN_OTHER && pn2->pn_next) |
1083 | /* Falling through to next case or default. */; | /* Falling through to next case or default. */; |
1084 | else | else |
# | Line 762 | Line 1100 |
1100 | ||
1101 | case TOK_COLON: | case TOK_COLON: |
1102 | case TOK_LEXICALSCOPE: | case TOK_LEXICALSCOPE: |
1103 | return HasFinalReturn(pn->pn_expr); | return HasFinalReturn(pn->expr()); |
1104 | ||
1105 | case TOK_THROW: | case TOK_THROW: |
1106 | return ENDS_IN_RETURN; | return ENDS_IN_RETURN; |
# | Line 806 | Line 1144 |
1144 | const char *name; | const char *name; |
1145 | ||
1146 | JS_ASSERT(tc->flags & TCF_IN_FUNCTION); | JS_ASSERT(tc->flags & TCF_IN_FUNCTION); |
1147 | if (tc->u.fun->atom) { | if (tc->fun->atom) { |
1148 | name = js_AtomToPrintableString(cx, tc->u.fun->atom); | name = js_AtomToPrintableString(cx, tc->fun->atom); |
1149 | } else { | } else { |
1150 | errnum = anonerrnum; | errnum = anonerrnum; |
1151 | name = NULL; | name = NULL; |
1152 | } | } |
1153 | return js_ReportCompileErrorNumber(cx, TS(tc->parseContext), NULL, flags, | return js_ReportCompileErrorNumber(cx, TS(tc->compiler), NULL, flags, |
1154 | errnum, name); | errnum, name); |
1155 | } | } |
1156 | ||
# | Line 849 | Line 1187 |
1187 | if (CURRENT_TOKEN(ts).type == TOK_LC) { | if (CURRENT_TOKEN(ts).type == TOK_LC) { |
1188 | pn = Statements(cx, ts, tc); | pn = Statements(cx, ts, tc); |
1189 | } else { | } else { |
1190 | pn = NewParseNode(cx, ts, PN_UNARY, tc); | pn = NewParseNode(PN_UNARY, tc); |
1191 | if (pn) { | if (pn) { |
1192 | pn->pn_kid = AssignExpr(cx, ts, tc); | pn->pn_kid = AssignExpr(cx, ts, tc); |
1193 | if (!pn->pn_kid) { | if (!pn->pn_kid) { |
# | Line 873 | Line 1211 |
1211 | #endif | #endif |
1212 | ||
1213 | if (pn) { | if (pn) { |
1214 | JS_ASSERT(!(tc->topStmt->flags & SIF_SCOPE)); | |
1215 | js_PopStatement(tc); | js_PopStatement(tc); |
1216 | pn->pn_pos.begin.lineno = firstLine; | pn->pn_pos.begin.lineno = firstLine; |
1217 | ||
# | Line 883 | Line 1222 |
1222 | } | } |
1223 | } | } |
1224 | ||
1225 | tc->flags = oldflags | (tc->flags & (TCF_FUN_FLAGS | TCF_HAS_DEFXMLNS)); | tc->flags = oldflags | (tc->flags & TCF_FUN_FLAGS); |
1226 | return pn; | return pn; |
1227 | } | } |
1228 | ||
1229 | static JSAtomListElement * | |
1230 | MakePlaceholder(JSParseNode *pn, JSTreeContext *tc) | |
1231 | { | |
1232 | JSAtomListElement *ale = tc->lexdeps.add(tc->compiler, pn->pn_atom); | |
1233 | if (!ale) | |
1234 | return NULL; | |
1235 | ||
1236 | JSDefinition *dn = (JSDefinition *) | |
1237 | NewNameNode(tc->compiler->context, TS(tc->compiler), pn->pn_atom, tc); | |
1238 | if (!dn) | |
1239 | return NULL; | |
1240 | ||
1241 | ALE_SET_DEFN(ale, dn); | |
1242 | dn->pn_defn = true; | |
1243 | dn->pn_dflags |= PND_PLACEHOLDER; | |
1244 | return ale; | |
1245 | } | |
1246 | ||
1247 | static bool | |
1248 | Define(JSParseNode *pn, JSAtom *atom, JSTreeContext *tc, bool let = false) | |
1249 | { | |
1250 | JS_ASSERT(!pn->pn_used); | |
1251 | JS_ASSERT_IF(pn->pn_defn, pn->isPlaceholder()); | |
1252 | ||
1253 | JSHashEntry **hep; | |
1254 | JSAtomListElement *ale = NULL; | |
1255 | JSAtomList *list = NULL; | |
1256 | ||
1257 | if (let) | |
1258 | ale = (list = &tc->decls)->rawLookup(atom, hep); | |
1259 | if (!ale) | |
1260 | ale = (list = &tc->lexdeps)->rawLookup(atom, hep); | |
1261 | ||
1262 | if (ale) { | |
1263 | JSDefinition *dn = ALE_DEFN(ale); | |
1264 | if (dn != pn) { | |
1265 | JSParseNode **pnup = &dn->dn_uses; | |
1266 | JSParseNode *pnu; | |
1267 | uintN start = let ? pn->pn_blockid : tc->bodyid; | |
1268 | ||
1269 | while ((pnu = *pnup) != NULL && pnu->pn_blockid >= start) { | |
1270 | JS_ASSERT(pnu->pn_used); | |
1271 | pnu->pn_lexdef = (JSDefinition *) pn; | |
1272 | pn->pn_dflags |= pnu->pn_dflags & (PND_ASSIGNED | PND_FUNARG); | |
1273 | pnup = &pnu->pn_link; | |
1274 | } | |
1275 | ||
1276 | if (pnu != dn->dn_uses) { | |
1277 | *pnup = pn->dn_uses; | |
1278 | pn->dn_uses = dn->dn_uses; | |
1279 | dn->dn_uses = pnu; | |
1280 | ||
1281 | if ((!pnu || pnu->pn_blockid < tc->bodyid) && list != &tc->decls) | |
1282 | list->rawRemove(tc->compiler, ale, hep); | |
1283 | } | |
1284 | } | |
1285 | } | |
1286 | ||
1287 | ale = tc->decls.add(tc->compiler, atom, let ? JSAtomList::SHADOW : JSAtomList::UNIQUE); | |
1288 | if (!ale) | |
1289 | return false; | |
1290 | ALE_SET_DEFN(ale, pn); | |
1291 | pn->pn_defn = true; | |
1292 | pn->pn_dflags &= ~PND_PLACEHOLDER; | |
1293 | return true; | |
1294 | } | |
1295 | ||
1296 | static void | |
1297 | LinkUseToDef(JSParseNode *pn, JSDefinition *dn, JSTreeContext *tc) | |
1298 | { | |
1299 | JS_ASSERT(!pn->pn_used); | |
1300 | JS_ASSERT(!pn->pn_defn); | |
1301 | JS_ASSERT(pn != dn->dn_uses); | |
1302 | pn->pn_link = dn->dn_uses; | |
1303 | dn->dn_uses = pn; | |
1304 | pn->pn_used = true; | |
1305 | pn->pn_lexdef = dn; | |
1306 | } | |
1307 | ||
1308 | static void | |
1309 | ForgetUse(JSParseNode *pn) | |
1310 | { | |
1311 | if (!pn->pn_used) { | |
1312 | JS_ASSERT(!pn->pn_defn); | |
1313 | return; | |
1314 | } | |
1315 | ||
1316 | JSParseNode **pnup = &pn->lexdef()->dn_uses; | |
1317 | JSParseNode *pnu; | |
1318 | while ((pnu = *pnup) != pn) | |
1319 | pnup = &pnu->pn_link; | |
1320 | *pnup = pn->pn_link; | |
1321 | pn->pn_used = false; | |
1322 | } | |
1323 | ||
1324 | static JSParseNode * | |
1325 | MakeAssignment(JSParseNode *pn, JSParseNode *rhs, JSTreeContext *tc) | |
1326 | { | |
1327 | JSParseNode *lhs = NewOrRecycledNode(tc); | |
1328 | if (!lhs) | |
1329 | return NULL; | |
1330 | *lhs = *pn; | |
1331 | ||
1332 | if (pn->pn_used) { | |
1333 | JSDefinition *dn = pn->pn_lexdef; | |
1334 | JSParseNode **pnup = &dn->dn_uses; | |
1335 | ||
1336 | while (*pnup != pn) | |
1337 | pnup = &(*pnup)->pn_link; | |
1338 | *pnup = lhs; | |
1339 | lhs->pn_link = pn->pn_link; | |
1340 | pn->pn_link = NULL; | |
1341 | } | |
1342 | ||
1343 | pn->pn_type = TOK_ASSIGN; | |
1344 | pn->pn_op = JSOP_NOP; | |
1345 | pn->pn_arity = PN_BINARY; | |
1346 | pn->pn_used = pn->pn_defn = false; | |
1347 | pn->pn_left = lhs; | |
1348 | pn->pn_right = rhs; | |
1349 | return lhs; | |
1350 | } | |
1351 | ||
1352 | static JSParseNode * | |
1353 | MakeDefIntoUse(JSDefinition *dn, JSParseNode *pn, JSAtom *atom, JSTreeContext *tc) | |
1354 | { | |
1355 | /* | |
1356 | * If dn is var, const, or let, and it has an initializer, then we must | |
1357 | * rewrite it to be an assignment node, whose freshly allocated left-hand | |
1358 | * side becomes a use of pn. | |
1359 | */ | |
1360 | if (dn->isBindingForm()) { | |
1361 | JSParseNode *rhs = dn->expr(); | |
1362 | if (rhs) { | |
1363 | JSParseNode *lhs = MakeAssignment(dn, rhs, tc); | |
1364 | if (!lhs) | |
1365 | return NULL; | |
1366 | //pn->dn_uses = lhs; | |
1367 | dn = (JSDefinition *) lhs; | |
1368 | } | |
1369 | ||
1370 | dn->pn_op = (js_CodeSpec[dn->pn_op].format & JOF_SET) ? JSOP_SETNAME : JSOP_NAME; | |
1371 | } else if (dn->kind() == JSDefinition::FUNCTION) { | |
1372 | JS_ASSERT(dn->isTopLevel()); | |
1373 | JS_ASSERT(dn->pn_op == JSOP_NOP); | |
1374 | ||
1375 | dn->pn_u.name.funbox2 = dn->pn_funbox; | |
1376 | dn->pn_u.name.expr2 = dn->pn_expr; | |
1377 | ||
1378 | dn->pn_type = TOK_NAME; | |
1379 | dn->pn_arity = PN_NAME; | |
1380 | dn->pn_atom = atom; | |
1381 | } | |
1382 | ||
1383 | /* Now make dn no longer a definition, rather a use of pn. */ | |
1384 | JS_ASSERT(dn->pn_type == TOK_NAME); | |
1385 | JS_ASSERT(dn->pn_arity == PN_NAME); | |
1386 | JS_ASSERT(dn->pn_atom == atom); | |
1387 | ||
1388 | for (JSParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) { | |
1389 | JS_ASSERT(pnu->pn_used); | |
1390 | JS_ASSERT(!pnu->pn_defn); | |
1391 | pnu->pn_lexdef = (JSDefinition *) pn; | |
1392 | pn->pn_dflags |= pnu->pn_dflags & (PND_ASSIGNED | PND_FUNARG); | |
1393 | } | |
1394 | pn->pn_dflags |= dn->pn_dflags & (PND_ASSIGNED | PND_FUNARG); | |
1395 | pn->dn_uses = dn; | |
1396 | ||
1397 | dn->pn_defn = false; | |
1398 | dn->pn_used = true; | |
1399 | dn->pn_lexdef = (JSDefinition *) pn; | |
1400 | dn->pn_cookie = FREE_UPVAR_COOKIE; | |
1401 | dn->pn_dflags &= ~PND_BOUND; | |
1402 | return dn; | |
1403 | } | |
1404 | ||
1405 | static bool | |
1406 | DefineArg(JSParseNode *pn, JSAtom *atom, uintN i, JSTreeContext *tc) | |
1407 | { | |
1408 | JSParseNode *argpn, *argsbody; | |
1409 | ||
1410 | /* Flag tc so we don't have to lookup arguments on every use. */ | |
1411 | if (atom == tc->compiler->context->runtime->atomState.argumentsAtom) | |
1412 | tc->flags |= TCF_FUN_PARAM_ARGUMENTS; | |
1413 | ||
1414 | /* | |
1415 | * Make an argument definition node, distinguished by being in tc->decls | |
1416 | * but having TOK_NAME type and JSOP_NOP op. Insert it in a TOK_ARGSBODY | |
1417 | * list node returned via pn->pn_body. | |
1418 | */ | |
1419 | argpn = NewNameNode(tc->compiler->context, TS(tc->compiler), atom, tc); | |
1420 | if (!argpn) | |
1421 | return false; | |
1422 | JS_ASSERT(PN_TYPE(argpn) == TOK_NAME && PN_OP(argpn) == JSOP_NOP); | |
1423 | ||
1424 | /* Arguments are initialized by definition. */ | |
1425 | argpn->pn_dflags |= PND_INITIALIZED; | |
1426 | if (!Define(argpn, atom, tc)) | |
1427 | return false; | |
1428 | ||
1429 | argsbody = pn->pn_body; | |
1430 | if (!argsbody) { | |
1431 | argsbody = NewParseNode(PN_LIST, tc); | |
1432 | if (!argsbody) | |
1433 | return false; | |
1434 | argsbody->pn_type = TOK_ARGSBODY; | |
1435 | argsbody->pn_op = JSOP_NOP; | |
1436 | argsbody->makeEmpty(); | |
1437 | pn->pn_body = argsbody; | |
1438 | } | |
1439 | argsbody->append(argpn); | |
1440 | ||
1441 | argpn->pn_op = JSOP_GETARG; | |
1442 | argpn->pn_cookie = MAKE_UPVAR_COOKIE(tc->staticLevel, i); | |
1443 | argpn->pn_dflags |= PND_BOUND; | |
1444 | return true; | |
1445 | } | |
1446 | ||
1447 | /* | /* |
1448 | * Compile a JS function body, which might appear as the value of an event | * Compile a JS function body, which might appear as the value of an event |
1449 | * handler attribute in an HTML <INPUT> tag. | * handler attribute in an HTML <INPUT> tag. |
1450 | */ | */ |
1451 | JSBool | bool |
1452 | js_CompileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipals *principals, | JSCompiler::compileFunctionBody(JSContext *cx, JSFunction *fun, JSPrincipals *principals, |
1453 | const jschar *chars, size_t length, | const jschar *chars, size_t length, |
1454 | const char *filename, uintN lineno) | const char *filename, uintN lineno) |
1455 | { | { |
1456 | JSParseContext pc; | JSCompiler jsc(cx, principals); |
JSArenaPool codePool, notePool; | ||
JSCodeGenerator funcg; | ||
JSParseNode *pn; | ||
1457 | ||
1458 | if (!js_InitParseContext(cx, &pc, principals, NULL, chars, length, NULL, | if (!jsc.init(chars, length, NULL, filename, lineno)) |
1459 | filename, lineno)) { | return false; |
return JS_FALSE; | ||
} | ||
1460 | ||
1461 | /* No early return from this point until js_FinishParseContext call. */ | /* No early return from after here until the js_FinishArenaPool calls. */ |
1462 | JSArenaPool codePool, notePool; | |
1463 | JS_INIT_ARENA_POOL(&codePool, "code", 1024, sizeof(jsbytecode), | JS_INIT_ARENA_POOL(&codePool, "code", 1024, sizeof(jsbytecode), |
1464 | &cx->scriptStackQuota); | &cx->scriptStackQuota); |
1465 | JS_INIT_ARENA_POOL(¬ePool, "note", 1024, sizeof(jssrcnote), | JS_INIT_ARENA_POOL(¬ePool, "note", 1024, sizeof(jssrcnote), |
1466 | &cx->scriptStackQuota); | &cx->scriptStackQuota); |
1467 | js_InitCodeGenerator(cx, &funcg, &pc, &codePool, ¬ePool, | |
1468 | pc.tokenStream.lineno); | JSCodeGenerator funcg(&jsc, &codePool, ¬ePool, jsc.tokenStream.lineno); |
1469 | funcg.treeContext.flags |= TCF_IN_FUNCTION; | funcg.flags |= TCF_IN_FUNCTION; |
1470 | funcg.treeContext.u.fun = fun; | funcg.fun = fun; |
1471 | if (!GenerateBlockId(&funcg, funcg.bodyid)) | |
1472 | return NULL; | |
1473 | ||
1474 | /* FIXME: make Function format the source for a function definition. */ | |
1475 | jsc.tokenStream.tokens[0].type = TOK_NAME; | |
1476 | JSParseNode *fn = NewParseNode(PN_FUNC, &funcg); | |
1477 | if (fn) { | |
1478 | fn->pn_body = NULL; | |
1479 | fn->pn_cookie = FREE_UPVAR_COOKIE; | |
1480 | ||
1481 | uintN nargs = fun->nargs; | |
1482 | if (nargs) { | |
1483 | jsuword *names = js_GetLocalNameArray(cx, fun, &cx->tempPool); | |
1484 | if (!names) { | |
1485 | fn = NULL; | |
1486 | } else { | |
1487 | for (uintN i = 0; i < nargs; i++) { | |
1488 | JSAtom *name = JS_LOCAL_NAME_TO_ATOM(names[i]); | |
1489 | if (!DefineArg(fn, name, i, &funcg)) { | |
1490 | fn = NULL; | |
1491 | break; | |
1492 | } | |
1493 | } | |
1494 | } | |
1495 | } | |
1496 | } | |
1497 | ||
1498 | /* | /* |
1499 | * Farble the body so that it looks like a block statement to js_EmitTree, | * Farble the body so that it looks like a block statement to js_EmitTree, |
1500 | * which is called beneath FunctionBody; see Statements, further below in | * which is called from js_EmitFunctionBody (see jsemit.cpp). After we're |
1501 | * this file. FunctionBody pushes a STMT_BLOCK record around its call to | * done parsing, we must fold constants, analyze any nested functions, and |
1502 | * Statements, so Statements will not compile each statement as it loops | * generate code for this function, including a stop opcode at the end. |
* to save JSParseNode space -- it will not compile at all, only build a | ||
* JSParseNode tree. | ||
* | ||
* Therefore we must fold constants, allocate try notes, and generate code | ||
* for this function, including a stop opcode at the end. | ||
1503 | */ | */ |
1504 | CURRENT_TOKEN(&pc.tokenStream).type = TOK_LC; | CURRENT_TOKEN(&jsc.tokenStream).type = TOK_LC; |
1505 | pn = FunctionBody(cx, &pc.tokenStream, &funcg.treeContext); | JSParseNode *pn = fn ? FunctionBody(cx, &jsc.tokenStream, &funcg) : NULL; |
1506 | if (pn) { | if (pn) { |
1507 | if (!js_MatchToken(cx, &pc.tokenStream, TOK_EOF)) { | if (!js_MatchToken(cx, &jsc.tokenStream, TOK_EOF)) { |
1508 | js_ReportCompileErrorNumber(cx, &pc.tokenStream, NULL, | js_ReportCompileErrorNumber(cx, &jsc.tokenStream, NULL, |
1509 | JSREPORT_ERROR, JSMSG_SYNTAX_ERROR); | JSREPORT_ERROR, JSMSG_SYNTAX_ERROR); |
1510 | pn = NULL; | pn = NULL; |
1511 | } else if (!js_FoldConstants(cx, pn, &funcg)) { | |
1512 | /* js_FoldConstants reported the error already. */ | |
1513 | pn = NULL; | |
1514 | } else if (funcg.functionList && | |
1515 | !jsc.analyzeFunctions(funcg.functionList, funcg.flags)) { | |
1516 | pn = NULL; | |
1517 | } else { | } else { |
1518 | if (!js_FoldConstants(cx, pn, &funcg.treeContext) || | if (fn->pn_body) { |
1519 | !js_EmitFunctionScript(cx, &funcg, pn)) { | JS_ASSERT(PN_TYPE(fn->pn_body) == TOK_ARGSBODY); |
1520 | pn = NULL; | fn->pn_body->append(pn); |
1521 | fn->pn_body->pn_pos = pn->pn_pos; | |
1522 | pn = fn->pn_body; | |
1523 | } | } |
1524 | ||
1525 | if (!js_EmitFunctionScript(cx, &funcg, pn)) | |
1526 | pn = NULL; | |
1527 | } | } |
1528 | } | } |
1529 | ||
1530 | /* Restore saved state and release code generation arenas. */ | /* Restore saved state and release code generation arenas. */ |
js_FinishCodeGenerator(cx, &funcg); | ||
1531 | JS_FinishArenaPool(&codePool); | JS_FinishArenaPool(&codePool); |
1532 | JS_FinishArenaPool(¬ePool); | JS_FinishArenaPool(¬ePool); |
js_FinishParseContext(cx, &pc); | ||
1533 | return pn != NULL; | return pn != NULL; |
1534 | } | } |
1535 | ||
# | Line 963 | Line 1546 |
1546 | (*Binder)(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc); | (*Binder)(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc); |
1547 | ||
1548 | struct BindData { | struct BindData { |
1549 | JSParseNode *pn; /* error source coordinate */ | BindData() : fresh(true) {} |
1550 | JSOp op; /* prolog bytecode or nop */ | |
1551 | Binder binder; /* binder, discriminates u */ | JSParseNode *pn; /* name node for definition processing and |
1552 | error source coordinates */ | |
1553 | JSOp op; /* prolog bytecode or nop */ | |
1554 | Binder binder; /* binder, discriminates u */ | |
1555 | union { | union { |
1556 | struct { | struct { |
1557 | uintN overflow; | uintN overflow; |
1558 | } let; | } let; |
1559 | } u; | }; |
1560 | bool fresh; | |
1561 | }; | }; |
1562 | ||
1563 | static JSBool | static JSBool |
BindArg(JSContext *cx, JSAtom *atom, JSTreeContext *tc) | ||
{ | ||
const char *name; | ||
/* | ||
* Check for a duplicate parameter name, a "feature" required by ECMA-262. | ||
*/ | ||
JS_ASSERT(tc->flags & TCF_IN_FUNCTION); | ||
if (js_LookupLocal(cx, tc->u.fun, atom, NULL) != JSLOCAL_NONE) { | ||
name = js_AtomToPrintableString(cx, atom); | ||
if (!name || | ||
!js_ReportCompileErrorNumber(cx, TS(tc->parseContext), NULL, | ||
JSREPORT_WARNING | JSREPORT_STRICT, | ||
JSMSG_DUPLICATE_FORMAL, | ||
name)) { | ||
return JS_FALSE; | ||
} | ||
} | ||
return js_AddLocal(cx, tc->u.fun, atom, JSLOCAL_ARG); | ||
} | ||
static JSBool | ||
1564 | BindLocalVariable(JSContext *cx, JSFunction *fun, JSAtom *atom, | BindLocalVariable(JSContext *cx, JSFunction *fun, JSAtom *atom, |
1565 | JSLocalKind localKind) | JSLocalKind localKind) |
1566 | { | { |
# | Line 1005 | Line 1569 |
1569 | /* | /* |
1570 | * Don't bind a variable with the hidden name 'arguments', per ECMA-262. | * Don't bind a variable with the hidden name 'arguments', per ECMA-262. |
1571 | * Instead 'var arguments' always restates the predefined property of the | * Instead 'var arguments' always restates the predefined property of the |
1572 | * activation objects with unhidden name 'arguments'. Assignment to such | * activation objects whose name is 'arguments'. Assignment to such a |
1573 | * a variable must be handled specially. | * variable must be handled specially. |
1574 | */ | */ |
1575 | if (atom == cx->runtime->atomState.argumentsAtom) | if (atom == cx->runtime->atomState.argumentsAtom) |
1576 | return JS_TRUE; | return JS_TRUE; |
# | Line 1027 | Line 1591 |
1591 | JSTreeContext *tc) | JSTreeContext *tc) |
1592 | { | { |
1593 | JSAtomListElement *ale; | JSAtomListElement *ale; |
1594 | const char *name; | JSParseNode *pn; |
1595 | ||
1596 | /* Flag tc so we don't have to lookup arguments on every use. */ | |
1597 | if (atom == tc->compiler->context->runtime->atomState.argumentsAtom) | |
1598 | tc->flags |= TCF_FUN_PARAM_ARGUMENTS; | |
1599 | ||
1600 | JS_ASSERT(tc->flags & TCF_IN_FUNCTION); | JS_ASSERT(tc->flags & TCF_IN_FUNCTION); |
1601 | ATOM_LIST_SEARCH(ale, &tc->decls, atom); | ale = tc->decls.lookup(atom); |
1602 | if (!ale) { | pn = data->pn; |
1603 | ale = js_IndexAtom(cx, atom, &tc->decls); | if (!ale && !Define(pn, atom, tc)) |
1604 | if (!ale) | return JS_FALSE; |
return JS_FALSE; | ||
ALE_SET_JSOP(ale, data->op); | ||
} | ||
1605 | ||
1606 | if (js_LookupLocal(cx, tc->u.fun, atom, NULL) != JSLOCAL_NONE) { | JSLocalKind localKind = js_LookupLocal(cx, tc->fun, atom, NULL); |
1607 | name = js_AtomToPrintableString(cx, atom); | if (localKind != JSLOCAL_NONE) { |
1608 | if (!name || | js_ReportCompileErrorNumber(cx, TS(tc->compiler), NULL, |
1609 | !js_ReportCompileErrorNumber(cx, TS(tc->parseContext), data->pn, | JSREPORT_ERROR, JSMSG_DESTRUCT_DUP_ARG); |
1610 | JSREPORT_WARNING | JSREPORT_STRICT, | return JS_FALSE; |
JSMSG_DUPLICATE_FORMAL, | ||
name)) { | ||
return JS_FALSE; | ||
} | ||
} else { | ||
if (!BindLocalVariable(cx, tc->u.fun, atom, JSLOCAL_VAR)) | ||
return JS_FALSE; | ||
1611 | } | } |
1612 | ||
1613 | uintN index = tc->fun->u.i.nvars; | |
1614 | if (!BindLocalVariable(cx, tc->fun, atom, JSLOCAL_VAR)) | |
1615 | return JS_FALSE; | |
1616 | pn->pn_op = JSOP_SETLOCAL; | |
1617 | pn->pn_cookie = MAKE_UPVAR_COOKIE(tc->staticLevel, index); | |
1618 | pn->pn_dflags |= PND_BOUND; | |
1619 | return JS_TRUE; | return JS_TRUE; |
1620 | } | } |
1621 | #endif /* JS_HAS_DESTRUCTURING */ | #endif /* JS_HAS_DESTRUCTURING */ |
1622 | ||
1623 | static JSFunction * | JSFunction * |
1624 | NewCompilerFunction(JSContext *cx, JSTreeContext *tc, JSAtom *atom, | JSCompiler::newFunction(JSTreeContext *tc, JSAtom *atom, uintN lambda) |
uintN lambda) | ||
1625 | { | { |
1626 | JSObject *parent; | JSObject *parent; |
1627 | JSFunction *fun; | JSFunction *fun; |
1628 | ||
1629 | JS_ASSERT((lambda & ~JSFUN_LAMBDA) == 0); | JS_ASSERT((lambda & ~JSFUN_LAMBDA) == 0); |
1630 | parent = (tc->flags & TCF_IN_FUNCTION) | |
1631 | ? FUN_OBJECT(tc->u.fun) | /* |
1632 | : tc->u.scopeChain; | * Find the global compilation context in order to pre-set the newborn |
1633 | fun = js_NewFunction(cx, NULL, NULL, 0, JSFUN_INTERPRETED | lambda, | * function's parent slot to tc->scopeChain. If the global context is a |
1634 | * compile-and-go one, we leave the pre-set parent intact; otherwise we | |
1635 | * clear parent and proto. | |
1636 | */ | |
1637 | while (tc->parent) | |
1638 | tc = tc->parent; | |
1639 | parent = (tc->flags & TCF_IN_FUNCTION) ? NULL : tc->scopeChain; | |
1640 | ||
1641 | fun = js_NewFunction(context, NULL, NULL, 0, JSFUN_INTERPRETED | lambda, | |
1642 | parent, atom); | parent, atom); |
1643 | ||
1644 | if (fun && !(tc->flags & TCF_COMPILE_N_GO)) { | if (fun && !(tc->flags & TCF_COMPILE_N_GO)) { |
1645 | STOBJ_CLEAR_PARENT(FUN_OBJECT(fun)); | STOBJ_CLEAR_PARENT(FUN_OBJECT(fun)); |
1646 | STOBJ_CLEAR_PROTO(FUN_OBJECT(fun)); | STOBJ_CLEAR_PROTO(FUN_OBJECT(fun)); |
# | Line 1075 | Line 1648 |
1648 | return fun; | return fun; |
1649 | } | } |
1650 | ||
1651 | static JSBool | |
1652 | MatchOrInsertSemicolon(JSContext *cx, JSTokenStream *ts) | |
1653 | { | |
1654 | JSTokenType tt; | |
1655 | ||
1656 | ts->flags |= TSF_OPERAND; | |
1657 | tt = js_PeekTokenSameLine(cx, ts); | |
1658 | ts->flags &= ~TSF_OPERAND; | |
1659 | if (tt == TOK_ERROR) | |
1660 | return JS_FALSE; | |
1661 | if (tt != TOK_EOF && tt != TOK_EOL && tt != TOK_SEMI && tt != TOK_RC) { | |
1662 | js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, | |
1663 | JSMSG_SEMI_BEFORE_STMNT); | |
1664 | return JS_FALSE; | |
1665 | } | |
1666 | (void) js_MatchToken(cx, ts, TOK_SEMI); | |
1667 | return JS_TRUE; | |
1668 | } | |
1669 | ||
1670 | bool | |
1671 | JSCompiler::analyzeFunctions(JSFunctionBox *funbox, uint16& tcflags) | |
1672 | { | |
1673 | if (!markFunArgs(funbox, tcflags)) | |
1674 | return false; | |
1675 | setFunctionKinds(funbox, tcflags); | |
1676 | return true; | |
1677 | } | |
1678 | ||
1679 | /* | |
1680 | * Mark as funargs any functions that reach up to one or more upvars across an | |
1681 | * already-known funarg. The parser will flag the o_m lambda as a funarg in: | |
1682 | * | |
1683 | * function f(o, p) { | |
1684 | * o.m = function o_m(a) { | |
1685 | * function g() { return p; } | |
1686 | * function h() { return a; } | |
1687 | * return g() + h(); | |
1688 | * } | |
1689 | * } | |
1690 | * | |
1691 | * but without this extra marking phase, function g will not be marked as a | |
1692 | * funarg since it is called from within its parent scope. But g reaches up to | |
1693 | * f's parameter p, so if o_m escapes f's activation scope, g does too and | |
1694 | * cannot use JSOP_GETUPVAR to reach p. In contast function h neither escapes | |
1695 | * nor uses an upvar "above" o_m's level. | |
1696 | * | |
1697 | * If function g itself contained lambdas that contained non-lambdas that reach | |
1698 | * up above its level, then those non-lambdas would have to be marked too. This | |
1699 | * process is potentially exponential in the number of functions, but generally | |
1700 | * not so complex. But it can't be done during a single recursive traversal of | |
1701 | * the funbox tree, so we must use a work queue. | |
1702 | * | |
1703 | * Return the minimal "skipmin" for funbox and its siblings. This is the delta | |
1704 | * between the static level of the bodies of funbox and its peers (which must | |
1705 | * be funbox->level + 1), and the static level of the nearest upvar among all | |
1706 | * the upvars contained by funbox and its peers. If there are no upvars, return | |
1707 | * FREE_STATIC_LEVEL. Thus this function never returns 0. | |
1708 | */ | |
1709 | static uintN | |
1710 | FindFunArgs(JSFunctionBox *funbox, int level, JSFunctionBoxQueue *queue) | |
1711 | { | |
1712 | uintN allskipmin = FREE_STATIC_LEVEL; | |
1713 | ||
1714 | do { | |
1715 | JSParseNode *fn = funbox->node; | |
1716 | JSFunction *fun = (JSFunction *) funbox->object; | |
1717 | int fnlevel = level; | |
1718 | ||
1719 | /* | |
1720 | * An eval can leak funbox, functions along its ancestor line, and its | |
1721 | * immediate kids. Since FindFunArgs uses DFS and the parser propagates | |
1722 | * TCF_FUN_HEAVYWEIGHT bottom up, funbox's ancestor function nodes have | |
1723 | * already been marked as funargs by this point. Therefore we have to | |
1724 | * flag only funbox->node and funbox->kids' nodes here. | |
1725 | */ | |
1726 | if (funbox->tcflags & TCF_FUN_HEAVYWEIGHT) { | |
1727 | fn->setFunArg(); | |
1728 | for (JSFunctionBox *kid = funbox->kids; kid; kid = kid->siblings) | |
1729 | kid->node->setFunArg(); | |
1730 | } | |
1731 | ||
1732 | /* | |
1733 | * Compute in skipmin the least distance from fun's static level up to | |
1734 | * an upvar, whether used directly by fun, or indirectly by a function | |
1735 | * nested in fun. | |
1736 | */ | |
1737 | uintN skipmin = FREE_STATIC_LEVEL; | |
1738 | JSParseNode *pn = fn->pn_body; | |
1739 | ||
1740 | if (pn->pn_type == TOK_UPVARS) { | |
1741 | JSAtomList upvars(pn->pn_names); | |
1742 | JS_ASSERT(upvars.count != 0); | |
1743 | ||
1744 | JSAtomListIterator iter(&upvars); | |
1745 | JSAtomListElement *ale; | |
1746 | ||
1747 | while ((ale = iter()) != NULL) { | |
1748 | JSDefinition *lexdep = ALE_DEFN(ale)->resolve(); | |
1749 | ||
1750 | if (!lexdep->isFreeVar()) { | |
1751 | uintN upvarLevel = lexdep->frameLevel(); | |
1752 | ||
1753 | if (int(upvarLevel) <= fnlevel) | |
1754 | fn->setFunArg(); | |
1755 | ||
1756 | uintN skip = (funbox->level + 1) - upvarLevel; | |
1757 | if (skip < skipmin) | |
1758 | skipmin = skip; | |
1759 | } | |
1760 | } | |
1761 | } | |
1762 | ||
1763 | /* | |
1764 | * If this function escapes, whether directly (the parser detects such | |
1765 | * escapes) or indirectly (because this non-escaping function uses an | |
1766 | * upvar that reaches across an outer function boundary where the outer | |
1767 | * function escapes), enqueue it for further analysis, and bump fnlevel | |
1768 | * to trap any non-escaping children. | |
1769 | */ | |
1770 | if (fn->isFunArg()) { | |
1771 | queue->push(funbox); | |
1772 | fnlevel = int(funbox->level); | |
1773 | } | |
1774 | ||
1775 | /* | |
1776 | * Now process the current function's children, and recalibrate their | |
1777 | * cumulative skipmin to be relative to the current static level. | |
1778 | */ | |
1779 | if (funbox->kids) { | |
1780 | uintN kidskipmin = FindFunArgs(funbox->kids, fnlevel, queue); | |
1781 | ||
1782 | JS_ASSERT(kidskipmin != 0); | |
1783 | if (kidskipmin != FREE_STATIC_LEVEL) { | |
1784 | --kidskipmin; | |
1785 | if (kidskipmin != 0 && kidskipmin < skipmin) | |
1786 | skipmin = kidskipmin; | |
1787 | } | |
1788 | } | |
1789 | ||
1790 | /* | |
1791 | * Finally, after we've traversed all of the current function's kids, | |
1792 | * minimize fun's skipmin against our accumulated skipmin. Do likewise | |
1793 | * with allskipmin, but minimize across funbox and all of its siblings, | |
1794 | * to compute our return value. | |
1795 | */ | |
1796 | if (skipmin != FREE_STATIC_LEVEL) { | |
1797 | fun->u.i.skipmin = skipmin; | |
1798 | if (skipmin < allskipmin) | |
1799 | allskipmin = skipmin; | |
1800 | } | |
1801 | } while ((funbox = funbox->siblings) != NULL); | |
1802 | ||
1803 | return allskipmin; | |
1804 | } | |
1805 | ||
1806 | bool | |
1807 | JSCompiler::markFunArgs(JSFunctionBox *funbox, uintN tcflags) | |
1808 | { | |
1809 | JSFunctionBoxQueue queue; | |
1810 | if (!queue.init(functionCount)) | |
1811 | return false; | |
1812 | ||
1813 | FindFunArgs(funbox, -1, &queue); | |
1814 | while ((funbox = queue.pull()) != NULL) { | |
1815 | JSParseNode *fn = funbox->node; | |
1816 | JS_ASSERT(fn->isFunArg()); | |
1817 | ||
1818 | JSParseNode *pn = fn->pn_body; | |
1819 | if (pn->pn_type == TOK_UPVARS) { | |
1820 | JSAtomList upvars(pn->pn_names); | |
1821 | JS_ASSERT(upvars.count != 0); | |
1822 | ||
1823 | JSAtomListIterator iter(&upvars); | |
1824 | JSAtomListElement *ale; | |
1825 | ||
1826 | while ((ale = iter()) != NULL) { | |
1827 | JSDefinition *lexdep = ALE_DEFN(ale)->resolve(); | |
1828 | ||
1829 | if (!lexdep->isFreeVar() && | |
1830 | !lexdep->isFunArg() && | |
1831 | lexdep->kind() == JSDefinition::FUNCTION) { | |
1832 | /* | |
1833 | * Mark this formerly-Algol-like function as an escaping | |
1834 | * function (i.e., as a funarg), because it is used from a | |
1835 | * funarg and therefore can not use JSOP_{GET,CALL}UPVAR to | |
1836 | * access upvars. | |
1837 | * | |
1838 | * Progress is guaranteed because we set the funarg flag | |
1839 | * here, which suppresses revisiting this function (thanks | |
1840 | * to the !lexdep->isFunArg() test just above). | |
1841 | */ | |
1842 | lexdep->setFunArg(); | |
1843 | ||
1844 | JSFunctionBox *afunbox = lexdep->pn_funbox; | |
1845 | queue.push(afunbox); | |
1846 | ||
1847 | /* | |
1848 | * Walk over nested functions again, now that we have | |
1849 | * changed the level across which it is unsafe to access | |
1850 | * upvars using the runtime dynamic link (frame chain). | |
1851 | */ | |
1852 | if (afunbox->kids) | |
1853 | FindFunArgs(afunbox->kids, afunbox->level, &queue); | |
1854 | } | |
1855 | } | |
1856 | } | |
1857 | } | |
1858 | return true; | |
1859 | } | |
1860 | ||
1861 | static uint32 | |
1862 | MinBlockId(JSParseNode *fn, uint32 id) | |
1863 | { | |
1864 | if (fn->pn_blockid < id) | |
1865 | return false; | |
1866 | if (fn->pn_defn) { | |
1867 | for (JSParseNode *pn = fn->dn_uses; pn; pn = pn->pn_link) { | |
1868 | if (pn->pn_blockid < id) | |
1869 | return false; | |
1870 | } | |
1871 | } | |
1872 | return true; | |
1873 | } | |
1874 | ||
1875 | static bool | |
1876 | OneBlockId(JSParseNode *fn, uint32 id) | |
1877 | { | |
1878 | if (fn->pn_blockid != id) | |
1879 | return false; | |
1880 | if (fn->pn_defn) { | |
1881 | for (JSParseNode *pn = fn->dn_uses; pn; pn = pn->pn_link) { | |
1882 | if (pn->pn_blockid != id) | |
1883 | return false; | |
1884 | } | |
1885 | } | |
1886 | return true; | |
1887 | } | |
1888 | ||
1889 | void | |
1890 | JSCompiler::setFunctionKinds(JSFunctionBox *funbox, uint16& tcflags) | |
1891 | { | |
1892 | #ifdef JS_FUNCTION_METERING | |
1893 | # define FUN_METER(x) JS_RUNTIME_METER(context->runtime, functionMeter.x) | |
1894 | #else | |
1895 | # define FUN_METER(x) ((void)0) | |
1896 | #endif | |
1897 | JSFunctionBox *parent = funbox->parent; | |
1898 | ||
1899 | for (;;) { | |
1900 | JSParseNode *fn = funbox->node; | |
1901 | ||
1902 | if (funbox->kids) | |
1903 | setFunctionKinds(funbox->kids, tcflags); | |
1904 | ||
1905 | JSParseNode *pn = fn->pn_body; | |
1906 | JSFunction *fun = (JSFunction *) funbox->object; | |
1907 | ||
1908 | FUN_METER(allfun); | |
1909 | if (funbox->tcflags & TCF_FUN_HEAVYWEIGHT) { | |
1910 | FUN_METER(heavy); | |
1911 | JS_ASSERT(FUN_KIND(fun) == JSFUN_INTERPRETED); | |
1912 | } else if (pn->pn_type != TOK_UPVARS) { | |
1913 | /* | |
1914 | * No lexical dependencies => null closure, for best performance. | |
1915 | * A null closure needs no scope chain, but alas we've coupled | |
1916 | * principals-finding to scope (for good fundamental reasons, but | |
1917 | * the implementation overloads the parent slot and we should fix | |
1918 | * that). See, e.g., the JSOP_LAMBDA case in jsinterp.cpp. | |
1919 | * | |
1920 | * In more detail: the ES3 spec allows the implementation to create | |
1921 | * "joined function objects", or not, at its discretion. But real- | |
1922 | * world implementations always create unique function objects for | |
1923 | * closures, and this can be detected via mutation. Open question: | |
1924 | * do popular implementations create unique function objects for | |
1925 | * null closures? | |
1926 | * | |
1927 | * FIXME: bug 476950. | |
1928 | */ | |
1929 | FUN_METER(nofreeupvar); | |
1930 | FUN_SET_KIND(fun, JSFUN_NULL_CLOSURE); | |
1931 | } else { | |
1932 | JSAtomList upvars(pn->pn_names); | |
1933 | JS_ASSERT(upvars.count != 0); | |
1934 | ||
1935 | JSAtomListIterator iter(&upvars); | |
1936 | JSAtomListElement *ale; | |
1937 | ||
1938 | if (!fn->isFunArg()) { | |
1939 | /* | |
1940 | * This function is Algol-like, it never escapes. So long as it | |
1941 | * does not assign to outer variables, it needs only an upvars | |
1942 | * array in its script and JSOP_{GET,CALL}UPVAR opcodes in its | |
1943 | * bytecode to reach up the frame stack at runtime based on | |
1944 | * those upvars' cookies. | |
1945 | * | |
1946 | * Any assignments to upvars from functions called by this one | |
1947 | * will be coherent because of the JSOP_{GET,CALL}UPVAR ops, | |
1948 | * which load from stack homes when interpreting or from native | |
1949 | * stack slots when executing a trace. | |
1950 | * | |
1951 | * We could add JSOP_SETUPVAR, etc., but it is uncommon for a | |
1952 | * nested function to assign to an outer lexical variable, so | |
1953 | * we defer adding yet more code footprint in the absence of | |
1954 | * evidence motivating these opcodes. | |
1955 | */ | |
1956 | bool mutation = !!(funbox->tcflags & TCF_FUN_SETS_OUTER_NAME); | |
1957 | uintN nupvars = 0; | |
1958 | ||
1959 | /* | |
1960 | * Check that at least one outer lexical binding was assigned | |
1961 | * to (global variables don't count). This is conservative: we | |
1962 | * could limit assignments to those in the current function, | |
1963 | * but that's too much work. As with flat closures (handled | |
1964 | * below), we optimize for the case where outer bindings are | |
1965 | * not reassigned anywhere. | |
1966 | */ | |
1967 | while ((ale = iter()) != NULL) { | |
1968 | JSDefinition *lexdep = ALE_DEFN(ale)->resolve(); | |
1969 | ||
1970 | if (!lexdep->isFreeVar()) { | |
1971 | JS_ASSERT(lexdep->frameLevel() <= funbox->level); | |
1972 | ++nupvars; | |
1973 | if (lexdep->isAssigned()) | |
1974 | break; | |
1975 | } | |
1976 | } | |
1977 | if (!ale) | |
1978 | mutation = false; | |
1979 | ||
1980 | if (nupvars == 0) { | |
1981 | FUN_METER(onlyfreevar); | |
1982 | FUN_SET_KIND(fun, JSFUN_NULL_CLOSURE); | |
1983 | } else if (!mutation && !(funbox->tcflags & TCF_FUN_IS_GENERATOR)) { | |
1984 | /* | |
1985 | * Algol-like functions can read upvars using the dynamic | |
1986 | * link (cx->fp/fp->down). They do not need to entrain and | |
1987 | * search their environment. | |
1988 | */ | |
1989 | FUN_METER(display); | |
1990 | FUN_SET_KIND(fun, JSFUN_NULL_CLOSURE); | |
1991 | } else { | |
1992 | if (!(funbox->tcflags & TCF_FUN_IS_GENERATOR)) | |
1993 | FUN_METER(setupvar); | |
1994 | } | |
1995 | } else { | |
1996 | uintN nupvars = 0; | |
1997 | ||
1998 | /* | |
1999 | * For each lexical dependency from this closure to an outer | |
2000 | * binding, analyze whether it is safe to copy the binding's | |
2001 | * value into a flat closure slot when the closure is formed. | |
2002 | */ | |
2003 | while ((ale = iter()) != NULL) { | |
2004 | JSDefinition *lexdep = ALE_DEFN(ale)->resolve(); | |
2005 | ||
2006 | if (!lexdep->isFreeVar()) { | |
2007 | ++nupvars; | |
2008 | ||
2009 | /* | |
2010 | * Consider the current function (the lambda, innermost | |
2011 | * below) using a var x defined two static levels up: | |
2012 | * | |
2013 | * function f() { | |
2014 | * // z = g(); | |
2015 | * var x = 42; | |
2016 | * function g() { | |
2017 | * return function () { return x; }; | |
2018 | * } | |
2019 | * return g(); | |
2020 | * } | |
2021 | * | |
2022 | * So long as (1) the initialization in 'var x = 42' | |
2023 | * dominates all uses of g and (2) x is not reassigned, | |
2024 | * it is safe to optimize the lambda to a flat closure. | |
2025 | * Uncommenting the early call to g makes it unsafe to | |
2026 | * so optimize (z could name a global setter that calls | |
2027 | * its argument). | |
2028 | */ | |
2029 | JSFunctionBox *afunbox = funbox; | |
2030 | uintN lexdepLevel = lexdep->frameLevel(); | |
2031 | ||
2032 | JS_ASSERT(lexdepLevel <= funbox->level); | |
2033 | while (afunbox->level != lexdepLevel) { | |
2034 | afunbox = afunbox->parent; | |
2035 | ||
2036 | /* | |
2037 | * afunbox can't be null because we are sure | |
2038 | * to find a function box whose level == lexdepLevel | |
2039 | * before walking off the top of the funbox tree. | |
2040 | * See bug 493260 comments 16-18. | |
2041 | * | |
2042 | * Assert but check anyway, to check future changes | |
2043 | * that bind eval upvars in the parser. | |
2044 | */ | |
2045 | JS_ASSERT(afunbox); | |
2046 | ||
2047 | /* | |
2048 | * If this function is reaching up across an | |
2049 | * enclosing funarg, we cannot make a flat | |
2050 | * closure. The display stops working once the | |
2051 | * funarg escapes. | |
2052 | */ | |
2053 | if (!afunbox || afunbox->node->isFunArg()) | |
2054 | goto break2; | |
2055 | } | |
2056 | ||
2057 | /* | |
2058 | * If afunbox's function (which is at the same level as | |
2059 | * lexdep) is in a loop, pessimistically assume the | |
2060 | * variable initializer may be in the same loop. A flat | |
2061 | * closure would then be unsafe, as the captured | |
2062 | * variable could be assigned after the closure is | |
2063 | * created. See bug 493232. | |
2064 | */ | |
2065 | if (afunbox->inLoop) | |
2066 | break; | |
2067 | ||
2068 | /* | |
2069 | * with and eval defeat lexical scoping; eval anywhere | |
2070 | * in a variable's scope can assign to it. Both defeat | |
2071 | * the flat closure optimization. The parser detects | |
2072 | * these cases and flags the function heavyweight. | |
2073 | */ | |
2074 | if ((afunbox->parent ? afunbox->parent->tcflags : tcflags) | |
2075 | & TCF_FUN_HEAVYWEIGHT) { | |
2076 | break; | |
2077 | } | |
2078 | ||
2079 | /* | |
2080 | * If afunbox's function is not a lambda, it will be | |
2081 | * hoisted, so it could capture the undefined value | |
2082 | * that by default initializes var/let/const | |
2083 | * bindings. And if lexdep is a function that comes at | |
2084 | * (meaning a function refers to its own name) or | |
2085 | * strictly after afunbox, we also break to defeat the | |
2086 | * flat closure optimization. | |
2087 | */ | |
2088 | JSFunction *afun = (JSFunction *) afunbox->object; | |
2089 | if (!(afun->flags & JSFUN_LAMBDA)) { | |
2090 | if (lexdep->isBindingForm()) | |
2091 | break; | |
2092 | if (lexdep->pn_pos >= afunbox->node->pn_pos) | |
2093 | break; | |
2094 | } | |
2095 | ||
2096 | if (!lexdep->isInitialized()) | |
2097 | break; | |
2098 | ||
2099 | JSDefinition::Kind lexdepKind = lexdep->kind(); | |
2100 | if (lexdepKind != JSDefinition::CONST) { | |
2101 | if (lexdep->isAssigned()) | |
2102 | break; | |
2103 | ||
2104 | /* | |
2105 | * Any formal could be mutated behind our back via | |
2106 | * the arguments object, so deoptimize if the outer | |
2107 | * function uses arguments. | |
2108 | * | |
2109 | * In a Function constructor call where the final | |
2110 | * argument -- the body source for the function to | |
2111 | * create -- contains a nested function definition | |
2112 | * or expression, afunbox->parent will be null. The | |
2113 | * body source might use |arguments| outside of any | |
2114 | * nested functions it may contain, so we have to | |
2115 | * check the tcflags parameter that was passed in | |
2116 | * from JSCompiler::compileFunctionBody. | |
2117 | */ | |
2118 | if (lexdepKind == JSDefinition::ARG && | |
2119 | ((afunbox->parent ? afunbox->parent->tcflags : tcflags) & | |
2120 | TCF_FUN_USES_ARGUMENTS)) { | |
2121 | break; | |
2122 | } | |
2123 | } | |
2124 | ||
2125 | /* | |
2126 | * Check quick-and-dirty dominance relation. Function | |
2127 | * definitions dominate their uses thanks to hoisting. | |
2128 | * Other binding forms hoist as undefined, of course, | |
2129 | * so check forward-reference and blockid relations. | |
2130 | */ | |
2131 | if (lexdepKind != JSDefinition::FUNCTION) { | |
2132 | /* | |
2133 | * Watch out for code such as | |
2134 | * | |
2135 | * (function () { | |
2136 | * ... | |
2137 | * var jQuery = ... = function (...) { | |
2138 | * return new jQuery.foo.bar(baz); | |
2139 | * } | |
2140 | * ... | |
2141 | * })(); | |
2142 | * | |
2143 | * where the jQuery var is not reassigned, but of | |
2144 | * course is not initialized at the time that the | |
2145 | * would-be-flat closure containing the jQuery | |
2146 | * upvar is formed. | |
2147 | */ | |
2148 | if (lexdep->pn_pos.end >= afunbox->node->pn_pos.end) | |
2149 | break; | |
2150 | ||
2151 | if (lexdep->isTopLevel() | |
2152 | ? !MinBlockId(afunbox->node, lexdep->pn_blockid) | |
2153 | : !lexdep->isBlockChild() || | |
2154 | !afunbox->node->isBlockChild() || | |
2155 | !OneBlockId(afunbox->node, lexdep->pn_blockid)) { | |
2156 | break; | |
2157 | } | |
2158 | } | |
2159 | } | |
2160 | } | |
2161 | ||
2162 | break2: | |
2163 | if (nupvars == 0) { | |
2164 | FUN_METER(onlyfreevar); | |
2165 | FUN_SET_KIND(fun, JSFUN_NULL_CLOSURE); | |
2166 | } else if (!ale) { | |
2167 | /* | |
2168 | * We made it all the way through the upvar loop, so it's | |
2169 | * safe to optimize to a flat closure. | |
2170 | */ | |
2171 | FUN_METER(flat); | |
2172 | FUN_SET_KIND(fun, JSFUN_FLAT_CLOSURE); | |
2173 | switch (PN_OP(fn)) { | |
2174 | case JSOP_DEFFUN: | |
2175 | fn->pn_op = JSOP_DEFFUN_FC; | |
2176 | break; | |
2177 | case JSOP_DEFLOCALFUN: | |
2178 | fn->pn_op = JSOP_DEFLOCALFUN_FC; | |
2179 | break; | |
2180 | case JSOP_LAMBDA: | |
2181 | fn->pn_op = JSOP_LAMBDA_FC; | |
2182 | break; | |
2183 | default: | |
2184 | /* js_EmitTree's case TOK_FUNCTION: will select op. */ | |
2185 | JS_ASSERT(PN_OP(fn) == JSOP_NOP); | |
2186 | } | |
2187 | } else { | |
2188 | FUN_METER(badfunarg); | |
2189 | } | |
2190 | } | |
2191 | } | |
2192 | ||
2193 | if (FUN_KIND(fun) == JSFUN_INTERPRETED) { | |
2194 | if (pn->pn_type != TOK_UPVARS) { | |
2195 | if (parent) | |
2196 | parent->tcflags |= TCF_FUN_HEAVYWEIGHT; | |
2197 | } else { | |
2198 | JSAtomList upvars(pn->pn_names); | |
2199 | JS_ASSERT(upvars.count != 0); | |
2200 | ||
2201 | JSAtomListIterator iter(&upvars); | |
2202 | JSAtomListElement *ale; | |
2203 | ||
2204 | /* | |
2205 | * One or more upvars cannot be safely snapshot into a flat | |
2206 | * closure's dslot (see JSOP_GETDSLOT), so we loop again over | |
2207 | * all upvars, and for each non-free upvar, ensure that its | |
2208 | * containing function has been flagged as heavyweight. | |
2209 | * | |
2210 | * The emitter must see TCF_FUN_HEAVYWEIGHT accurately before | |
2211 | * generating any code for a tree of nested functions. | |
2212 | */ | |
2213 | while ((ale = iter()) != NULL) { | |
2214 | JSDefinition *lexdep = ALE_DEFN(ale)->resolve(); | |
2215 | ||
2216 | if (!lexdep->isFreeVar()) { | |
2217 | JSFunctionBox *afunbox = funbox->parent; | |
2218 | uintN lexdepLevel = lexdep->frameLevel(); | |
2219 | ||
2220 | while (afunbox) { | |
2221 | /* | |
2222 | * NB: afunbox->level is the static level of | |
2223 | * the definition or expression of the function | |
2224 | * parsed into afunbox, not the static level of | |
2225 | * its body. Therefore we must add 1 to match | |
2226 | * lexdep's level to find the afunbox whose | |
2227 | * body contains the lexdep definition. | |
2228 | */ | |
2229 | if (afunbox->level + 1U == lexdepLevel || | |
2230 | (lexdepLevel == 0 && lexdep->isLet())) { | |
2231 | afunbox->tcflags |= TCF_FUN_HEAVYWEIGHT; | |
2232 | break; | |
2233 | } | |
2234 | afunbox = afunbox->parent; | |
2235 | } | |
2236 | if (!afunbox && (tcflags & TCF_IN_FUNCTION)) | |
2237 | tcflags |= TCF_FUN_HEAVYWEIGHT; | |
2238 | } | |
2239 | } | |
2240 | } | |
2241 | } | |
2242 | ||
2243 | funbox = funbox->siblings; | |
2244 | if (!funbox) | |
2245 | break; | |
2246 | JS_ASSERT(funbox->parent == parent); | |
2247 | } | |
2248 | #undef FUN_METER | |
2249 | } | |
2250 | ||
2251 | const char js_argument_str[] = "argument"; | |
2252 | const char js_variable_str[] = "variable"; | |
2253 | const char js_unknown_str[] = "unknown"; | |
2254 | ||
2255 | const char * | |
2256 | JSDefinition::kindString(Kind kind) | |
2257 | { | |
2258 | static const char *table[] = { | |
2259 | js_var_str, js_const_str, js_let_str, | |
2260 | js_function_str, js_argument_str, js_unknown_str | |
2261 | }; | |
2262 | ||
2263 | JS_ASSERT(unsigned(kind) <= unsigned(ARG)); | |
2264 | return table[kind]; | |
2265 | } | |
2266 | ||
2267 | static JSFunctionBox * | |
2268 | EnterFunction(JSParseNode *fn, JSTreeContext *tc, JSTreeContext *funtc, | |
2269 | JSAtom *funAtom = NULL, uintN lambda = JSFUN_LAMBDA) | |
2270 | { | |
2271 | JSFunction *fun = tc->compiler->newFunction(tc, funAtom, lambda); | |
2272 | if (!fun) | |
2273 | return NULL; | |
2274 | ||
2275 | /* Create box for fun->object early to protect against last-ditch GC. */ | |
2276 | JSFunctionBox *funbox = tc->compiler->newFunctionBox(FUN_OBJECT(fun), fn, tc); | |
2277 | if (!funbox) | |
2278 | return NULL; | |
2279 | ||
2280 | /* Initialize non-default members of funtc. */ | |
2281 | funtc->flags |= funbox->tcflags; | |
2282 | funtc->blockidGen = tc->blockidGen; | |
2283 | if (!GenerateBlockId(funtc, funtc->bodyid)) | |
2284 | return NULL; | |
2285 | funtc->fun = fun; | |
2286 | funtc->funbox = funbox; | |
2287 | funtc->parent = tc; | |
2288 | if (!SetStaticLevel(funtc, tc->staticLevel + 1)) | |
2289 | return NULL; | |
2290 | ||
2291 | return funbox; | |
2292 | } | |
2293 | ||
2294 | static bool | |
2295 | LeaveFunction(JSParseNode *fn, JSTreeContext *funtc, JSTreeContext *tc, | |
2296 | JSAtom *funAtom = NULL, uintN lambda = JSFUN_LAMBDA) | |
2297 | { | |
2298 | tc->blockidGen = funtc->blockidGen; | |
2299 | ||
2300 | fn->pn_funbox->tcflags |= funtc->flags & (TCF_FUN_FLAGS | TCF_COMPILE_N_GO); | |
2301 | ||
2302 | fn->pn_dflags |= PND_INITIALIZED; | |
2303 | JS_ASSERT_IF(tc->atTopLevel() && lambda == 0 && funAtom, | |
2304 | fn->pn_dflags & PND_TOPLEVEL); | |
2305 | if (!tc->topStmt || tc->topStmt->type == STMT_BLOCK) | |
2306 | fn->pn_dflags |= PND_BLOCKCHILD; | |
2307 | ||
2308 | /* | |
2309 | * Propagate unresolved lexical names up to tc->lexdeps, and save a copy | |
2310 | * of funtc->lexdeps in a TOK_UPVARS node wrapping the function's formal | |
2311 | * params and body. We do this only if there are lexical dependencies not | |
2312 | * satisfied by the function's declarations, to avoid penalizing functions | |
2313 | * that use only their arguments and other local bindings. | |
2314 | */ | |
2315 | if (funtc->lexdeps.count != 0) { | |
2316 | JSAtomListIterator iter(&funtc->lexdeps); | |
2317 | JSAtomListElement *ale; | |
2318 | int foundCallee = 0; | |
2319 | ||
2320 | while ((ale = iter()) != NULL) { | |
2321 | JSAtom *atom = ALE_ATOM(ale); | |
2322 | JSDefinition *dn = ALE_DEFN(ale); | |
2323 | JS_ASSERT(dn->isPlaceholder()); | |
2324 | ||
2325 | if (atom == funAtom && lambda != 0) { | |
2326 | dn->pn_op = JSOP_CALLEE; | |
2327 | dn->pn_cookie = MAKE_UPVAR_COOKIE(funtc->staticLevel, CALLEE_UPVAR_SLOT); | |
2328 | dn->pn_dflags |= PND_BOUND; | |
2329 | ||
2330 | /* | |
2331 | * If this named function expression uses its own name other | |
2332 | * than to call itself, flag this function as using arguments, | |
2333 | * as if it had used arguments.callee instead of its own name. | |
2334 | * | |
2335 | * This abuses the plain sense of TCF_FUN_USES_ARGUMENTS, but | |
2336 | * we are out of tcflags bits at the moment. If it deoptimizes | |
2337 | * code unfairly (see JSCompiler::setFunctionKinds, where this | |
2338 | * flag is interpreted in its broader sense, not only to mean | |
2339 | * "this function might leak arguments.callee"), we can perhaps | |
2340 | * try to work harder to add a TCF_FUN_LEAKS_ITSELF flag and | |
2341 | * use that more precisely, both here and for unnamed function | |
2342 | * expressions. | |
2343 | */ | |
2344 | if (dn->isFunArg()) | |
2345 | fn->pn_funbox->tcflags |= TCF_FUN_USES_ARGUMENTS; | |
2346 | foundCallee = 1; | |
2347 | continue; | |
2348 | } | |
2349 | ||
2350 | if (!(fn->pn_funbox->tcflags & TCF_FUN_SETS_OUTER_NAME) && | |
2351 | dn->isAssigned()) { | |
2352 | /* | |
2353 | * Make sure we do not fail to set TCF_FUN_SETS_OUTER_NAME if | |
2354 | * any use of dn in funtc assigns. See NoteLValue for the easy | |
2355 | * backward-reference case; this is the hard forward-reference | |
2356 | * case where we pay a higher price. | |
2357 | */ | |
2358 | for (JSParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) { | |
2359 | if (pnu->isAssigned() && pnu->pn_blockid >= funtc->bodyid) { | |
2360 | fn->pn_funbox->tcflags |= TCF_FUN_SETS_OUTER_NAME; | |
2361 | break; | |
2362 | } | |
2363 | } | |
2364 | } | |
2365 | ||
2366 | JSAtomListElement *outer_ale = tc->decls.lookup(atom); | |
2367 | if (!outer_ale) | |
2368 | outer_ale = tc->lexdeps.lookup(atom); | |
2369 | if (outer_ale) { | |
2370 | /* | |
2371 | * Insert dn's uses list at the front of outer_dn's list. | |
2372 | * | |
2373 | * Without loss of generality or correctness, we allow a dn to | |
2374 | * be in inner and outer lexdeps, since the purpose of lexdeps | |
2375 | * is one-pass coordination of name use and definition across | |
2376 | * functions, and if different dn's are used we'll merge lists | |
2377 | * when leaving the inner function. | |
2378 | * | |
2379 | * The dn == outer_dn case arises with generator expressions | |
2380 | * (see CompExprTransplanter::transplant, the PN_FUNC/PN_NAME | |
2381 | * case), and nowhere else, currently. | |
2382 | */ | |
2383 | JSDefinition *outer_dn = ALE_DEFN(outer_ale); | |
2384 | ||
2385 | if (dn != outer_dn) { | |
2386 | JSParseNode **pnup = &dn->dn_uses; | |
2387 | JSParseNode *pnu; | |
2388 | ||
2389 | while ((pnu = *pnup) != NULL) { | |
2390 | pnu->pn_lexdef = outer_dn; | |
2391 | pnup = &pnu->pn_link; | |
2392 | } | |
2393 | ||
2394 | /* | |
2395 | * Make dn be a use that redirects to outer_dn, because we | |
2396 | * can't replace dn with outer_dn in all the pn_namesets in | |
2397 | * the AST where it may be. Instead we make it forward to | |
2398 | * outer_dn. See JSDefinition::resolve. | |
2399 | */ | |
2400 | *pnup = outer_dn->dn_uses; | |
2401 | outer_dn->dn_uses = dn; | |
2402 | outer_dn->pn_dflags |= dn->pn_dflags & ~PND_PLACEHOLDER; | |
2403 | dn->pn_defn = false; | |
2404 | dn->pn_used = true; | |
2405 | dn->pn_lexdef = outer_dn; | |
2406 | } | |
2407 | } else { | |
2408 | /* Add an outer lexical dependency for ale's definition. */ | |
2409 | outer_ale = tc->lexdeps.add(tc->compiler, atom); | |
2410 | if (!outer_ale) | |
2411 | return false; | |
2412 | ALE_SET_DEFN(outer_ale, ALE_DEFN(ale)); | |
2413 | } | |
2414 | } | |
2415 | ||
2416 | if (funtc->lexdeps.count - foundCallee != 0) { | |
2417 | JSParseNode *body = fn->pn_body; | |
2418 | ||
2419 | fn->pn_body = NewParseNode(PN_NAMESET, tc); | |
2420 | if (!fn->pn_body) | |
2421 | return false; | |
2422 | ||
2423 | fn->pn_body->pn_type = TOK_UPVARS; | |
2424 | fn->pn_body->pn_pos = body->pn_pos; | |
2425 | if (foundCallee) | |
2426 | funtc->lexdeps.remove(tc->compiler, funAtom); | |
2427 | fn->pn_body->pn_names = funtc->lexdeps; | |
2428 | fn->pn_body->pn_tree = body; | |
2429 | } | |
2430 | ||
2431 | funtc->lexdeps.clear(); | |
2432 | } | |
2433 | ||
2434 | return true; | |
2435 | } | |
2436 | ||
2437 | static JSParseNode * | static JSParseNode * |
2438 | FunctionDef(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, | FunctionDef(JSContext *cx, JSTokenStream *ts, JSTreeContext *tc, |
2439 | uintN lambda) | uintN lambda) |
2440 | { | { |
2441 | JSOp op, prevop; | JSOp op; |
2442 | JSParseNode *pn, *body, *result; | JSParseNode *pn, *body, *result; |
2443 | JSTokenType tt; | JSTokenType tt; |
2444 | JSAtom *funAtom; | JSAtom *funAtom; |
JSParsedObjectBox *funpob; | ||
2445 | JSAtomListElement *ale; | JSAtomListElement *ale; |
JSFunction *fun; | ||
JSTreeContext funtc; | ||
2446 | #if JS_HAS_DESTRUCTURING | #if JS_HAS_DESTRUCTURING |
2447 | JSParseNode *item, *list = NULL; | JSParseNode *item, *list = NULL; |
2448 | bool destructuringArg = false, duplicatedArg = false; | |
2449 | #endif | #endif |
2450 | ||
2451 | /* Make a TOK_FUNCTION node. */ | /* Make a TOK_FUNCTION node. */ |
2452 | #if JS_HAS_GETTER_SETTER | #if JS_HAS_GETTER_SETTER |
2453 | op = CURRENT_TOKEN(ts).t_op; | op = CURRENT_TOKEN(ts).t_op; |
2454 | #endif | #endif |
2455 | pn = NewParseNode(cx, ts, PN_FUNC, tc); | pn = NewParseNode(PN_FUNC, tc); |
2456 | if (!pn) | if (!pn) |
2457 | return NULL; | return NULL; |
2458 | #ifdef DEBUG | pn->pn_body = NULL; |
2459 | pn->pn_index = (uint32) -1; | pn->pn_cookie = FREE_UPVAR_COOKIE; |
2460 | #endif | |
2461 | /* | |
2462 | * If a lambda, give up on JSOP_{GET,CALL}UPVAR usage unless this function | |
2463 | * is immediately applied (we clear PND_FUNARG if so -- see MemberExpr). | |
2464 | * | |
2465 | * Also treat function sub-statements (non-lambda, non-top-level functions) | |
2466 | * as escaping funargs, since we can't statically analyze their definitions | |
2467 | * and uses. | |
2468 | */ | |
2469 | bool topLevel = tc->atTopLevel(); | |
2470 | pn->pn_dflags = (lambda || !topLevel) ? PND_FUNARG : 0; | |
2471 | ||
2472 | /* Scan the optional function name into funAtom. */ | /* Scan the optional function name into funAtom. */ |
2473 | ts->flags |= TSF_KEYWORD_IS_NAME; | ts->flags |= TSF_KEYWORD_IS_NAME; |
# | Line 1123 | Line 2490 |
2490 | * avoid optimizing variable references that might name a function. | * avoid optimizing variable references that might name a function. |
2491 | */ | */ |
2492 | if (lambda == 0 && funAtom) { | if (lambda == 0 && funAtom) { |
2493 | ATOM_LIST_SEARCH(ale, &tc->decls, funAtom); | ale = tc->decls.lookup(funAtom); |
2494 | if (ale) { | if (ale) { |
2495 | prevop = ALE_JSOP(ale); | JSDefinition *dn = ALE_DEFN(ale); |
2496 | if (JS_HAS_STRICT_OPTION(cx) || prevop == JSOP_DEFCONST) { | JSDefinition::Kind dn_kind = dn->kind(); |
2497 | ||
2498 | JS_ASSERT(!dn->pn_used); | |
2499 | JS_ASSERT(dn->pn_defn); | |
2500 | ||
2501 | if (JS_HAS_STRICT_OPTION(cx) || dn_kind == JSDefinition::CONST) { | |
2502 | const char *name = js_AtomToPrintableString(cx, funAtom); | const char *name = js_AtomToPrintableString(cx, funAtom); |
2503 | if (!name || | if (!name || |
2504 | !js_ReportCompileErrorNumber(cx, ts, NULL, | !js_ReportCompileErrorNumber(cx, ts, NULL, |
2505 | (prevop != JSOP_DEFCONST) | (dn_kind != JSDefinition::CONST) |
2506 | ? JSREPORT_WARNING | | ? JSREPORT_WARNING | JSREPORT_STRICT |
JSREPORT_STRICT | ||
2507 | : JSREPORT_ERROR, | : JSREPORT_ERROR, |
2508 | JSMSG_REDECLARED_VAR, | JSMSG_REDECLARED_VAR, |
2509 | (prevop == JSOP_DEFFUN) | JSDefinition::kindString(dn_kind), |
? js_function_str | ||
: (prevop == JSOP_DEFCONST) | ||
? js_const_str | ||
: js_var_str, | ||
2510 | name)) { | name)) { |
2511 | return NULL; | return NULL; |
2512 | } | } |
2513 | } | } |
2514 | if (!AT_TOP_LEVEL(tc) && prevop == JSOP_DEFVAR) | |
2515 | tc->flags |= TCF_FUN_CLOSURE_VS_VAR; | if (topLevel) { |
2516 | } else { | ALE_SET_DEFN(ale, pn); |
2517 | ale = js_IndexAtom(cx, funAtom, &tc->decls); | pn->pn_defn = true; |
2518 | if (!ale) | pn->dn_uses = dn; /* dn->dn_uses is now pn_link */ |
2519 | ||
2520 | if (!MakeDefIntoUse(dn, pn, funAtom, tc)) | |
2521 | return NULL; | |
2522 | } | |
2523 | } else if (topLevel) { | |
2524 | /* | |
2525 | * If this function was used before it was defined, claim the | |
2526 | * pre-created definition node for this function that PrimaryExpr | |
2527 | * put in tc->lexdeps on first forward reference, and recycle pn. | |
2528 | */ | |
2529 | JSHashEntry **hep; | |
2530 | ||
2531 | ale = tc->lexdeps.rawLookup(funAtom, hep); | |
2532 | if (ale) { | |
2533 | JSDefinition *fn = ALE_DEFN(ale); | |
2534 | ||
2535 | JS_ASSERT(fn->pn_defn); | |
2536 | fn->pn_type = TOK_FUNCTION; | |
2537 | fn->pn_arity = PN_FUNC; | |
2538 | fn->pn_pos.begin = pn->pn_pos.begin; | |
2539 | fn->pn_body = NULL; | |
2540 | fn->pn_cookie = FREE_UPVAR_COOKIE; | |
2541 | ||
2542 | tc->lexdeps.rawRemove(tc->compiler, ale, hep); | |
2543 | RecycleTree(pn, tc); | |
2544 | pn = fn; | |
2545 | } | |
2546 | ||
2547 | if (!Define(pn, funAtom, tc)) | |
2548 | return NULL; | return NULL; |
2549 | } | } |
ALE_SET_JSOP(ale, JSOP_DEFFUN); | ||
2550 | ||
2551 | /* | /* |
2552 | * A function nested at top level inside another's body needs only a | * A function nested at top level inside another's body needs only a |
# | Line 1161 | Line 2556 |
2556 | * wins when jsemit.c's BindNameToSlot can optimize a JSOP_NAME into a | * wins when jsemit.c's BindNameToSlot can optimize a JSOP_NAME into a |
2557 | * JSOP_GETLOCAL bytecode). | * JSOP_GETLOCAL bytecode). |
2558 | */ | */ |
2559 | if (AT_TOP_LEVEL(tc) && (tc->flags & TCF_IN_FUNCTION)) { | if (topLevel) { |
2560 | JSLocalKind localKind; | pn->pn_dflags |= PND_TOPLEVEL; |
2561 | ||
2562 | /* | if (tc->flags & TCF_IN_FUNCTION) { |
2563 | * Define a property on the outer function so that BindNameToSlot | JSLocalKind localKind; |
2564 | * can properly optimize accesses. Note that we need a variable, | uintN index; |
2565 | * not an argument, for the function statement. Thus we add a | |
2566 | * variable even if the parameter with the given name already | /* |
2567 | * exists. | * Define a local in the outer function so that BindNameToSlot |
2568 | */ | * can properly optimize accesses. Note that we need a local |
2569 | localKind = js_LookupLocal(cx, tc->u.fun, funAtom, NULL); | * variable, not an argument, for the function statement. Thus |
2570 | if (localKind == JSLOCAL_NONE || localKind == JSLOCAL_ARG) { | * we add a variable even if a parameter with the given name |
2571 | if (!js_AddLocal(cx, tc->u.fun, funAtom, JSLOCAL_VAR)) | * already exists. |
2572 | return NULL; | */ |
2573 | localKind = js_LookupLocal(cx, tc->fun, funAtom, &index); | |
2574 | switch (localKind) { | |
2575 | case JSLOCAL_NONE: | |
2576 | case JSLOCAL_ARG: | |
2577 | index = tc->fun->u.i.nvars; | |
2578 | if (!js_AddLocal(cx, tc->fun, funAtom, JSLOCAL_VAR)) | |
2579 | return NULL; | |
2580 | /* FALL THROUGH */ | |
2581 | ||
2582 | case JSLOCAL_VAR: | |
2583 | pn->pn_cookie = MAKE_UPVAR_COOKIE(tc->staticLevel, index); | |
2584 | pn->pn_dflags |= PND_BOUND; | |
2585 | break; | |
2586 | ||
2587 | default:; | |
2588 | } | |
2589 | } | } |
2590 | } | } |
2591 | } | } |
2592 | ||
2593 | fun = NewCompilerFunction(cx, tc, funAtom, lambda); | /* Initialize early for possible flags mutation via DestructuringExpr. */ |
2594 | if (!fun) | JSTreeContext funtc(tc->compiler); |
2595 | ||
2596 | JSFunctionBox *funbox = EnterFunction(pn, tc, &funtc, funAtom, lambda); | |
2597 | if (!funbox) | |
2598 | return NULL; | return NULL; |
2599 | ||
2600 | JSFunction *fun = (JSFunction *) funbox->object; | |
2601 | ||
2602 | #if JS_HAS_GETTER_SETTER | #if JS_HAS_GETTER_SETTER |
2603 | if (op != JSOP_NOP) | if (op != JSOP_NOP) |
2604 | fun->flags |= (op == JSOP_GETTER) ? JSPROP_GETTER : JSPROP_SETTER; | fun->flags |= (op == JSOP_GETTER) ? JSPROP_GETTER : JSPROP_SETTER; |
2605 | #endif | #endif |
2606 | ||
/* | ||
* Create wrapping box for fun->object early to protect against a | ||
* last-ditch GC. | ||
*/ | ||
funpob = js_NewParsedObjectBox(cx, tc->parseContext, FUN_OBJECT(fun)); | ||
if (!funpob) | ||
return NULL; | ||
/* Initialize early for possible flags mutation via DestructuringExpr. */ | ||
TREE_CONTEXT_INIT(&funtc, tc->parseContext); | ||
funtc.flags |= TCF_IN_FUNCTION | (tc->flags & TCF_COMPILE_N_GO); | ||
funtc.u.fun = fun; | ||
2607 | /* Now parse formal argument list and compute fun->nargs. */ | /* Now parse formal argument list and compute fun->nargs. */ |
2608 | MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_FORMAL); | MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_FORMAL); |
2609 | if (!js_MatchToken(cx, ts, TOK_RP)) { | if (!js_MatchToken(cx, ts, TOK_RP)) { |
# | Line 1215 | Line 2618 |
2618 | JSParseNode *lhs, *rhs; | JSParseNode *lhs, *rhs; |
2619 | jsint slot; | jsint slot; |
2620 | ||
2621 | /* See comment below in the TOK_NAME case. */ | |
2622 | if (duplicatedArg) | |
2623 | goto report_dup_and_destructuring; | |
2624 | destructuringArg = true; | |
2625 | ||
2626 | /* | /* |
2627 | * A destructuring formal parameter turns into one or more | * A destructuring formal parameter turns into one or more |
2628 | * local variables initialized from properties of a single | * local variables initialized from properties of a single |
# | Line 1241 | Line 2649 |
2649 | * anonymous positional parameter into the destructuring | * anonymous positional parameter into the destructuring |
2650 | * left-hand-side expression and accumulate it in list. | * left-hand-side expression and accumulate it in list. |
2651 | */ | */ |
2652 | rhs = NewParseNode(cx, ts, PN_NAME, tc); | rhs = NewNameNode(cx, ts, cx->runtime->atomState.emptyAtom, &funtc); |
2653 | if (!rhs) | if (!rhs) |
2654 | return NULL; | return NULL; |
2655 | rhs->pn_type = TOK_NAME; | rhs->pn_type = TOK_NAME; |
2656 | rhs->pn_op = JSOP_GETARG; | rhs->pn_op = JSOP_GETARG; |
2657 | rhs->pn_atom = cx->runtime->atomState.emptyAtom; | rhs->pn_cookie = MAKE_UPVAR_COOKIE(funtc.staticLevel, slot); |
2658 | rhs->pn_slot = slot; | rhs->pn_dflags |= PND_BOUND; |
2659 | ||
2660 | item = NewBinary(cx, TOK_ASSIGN, JSOP_NOP, lhs, rhs, tc); | item = NewBinary(TOK_ASSIGN, JSOP_NOP, lhs, rhs, &funtc); |
2661 | if (!item) | if (!item) |
2662 | return NULL; | return NULL; |
2663 | if (!list) { | if (!list) { |
2664 | list = NewParseNode(cx, ts, PN_LIST, tc); | list = NewParseNode(PN_LIST, &funtc); |
2665 | if (!list) | if (!list) |
2666 | return NULL; | return NULL; |
2667 | list->pn_type = TOK_COMMA; | list->pn_type = TOK_COMMA; |
2668 | PN_INIT_LIST(list); | list->makeEmpty(); |
2669 | } | } |
2670 | PN_APPEND(list, item); | list->append(item); |
2671 | break; | break; |
2672 | } | } |
2673 | #endif /* JS_HAS_DESTRUCTURING */ | #endif /* JS_HAS_DESTRUCTURING */ |
2674 | ||
2675 | case TOK_NAME: | case TOK_NAME: |
2676 | if (!BindArg(cx, CURRENT_TOKEN(ts).t_atom, &funtc)) | { |
2677 | /* | |
2678 | * Check for a duplicate parameter name, a "feature" that | |
2679 | * ECMA-262 requires. This is a SpiderMonkey strict warning, | |
2680 | * soon to be an ES3.1 strict error. | |
2681 | * | |
2682 | * Further, if any argument is a destructuring pattern, forbid | |
2683 | * duplicates. We will report the error either now if we have | |
2684 | * seen a destructuring pattern already, or later when we find | |
2685 | * the first pattern. | |
2686 | */ | |
2687 | JSAtom *atom = CURRENT_TOKEN(ts).t_atom; | |
2688 | if (JS_HAS_STRICT_OPTION(cx) && | |
2689 | js_LookupLocal(cx, fun, atom, NULL) != JSLOCAL_NONE) { | |
2690 | #if JS_HAS_DESTRUCTURING | |
2691 | if (destructuringArg) | |
2692 | goto report_dup_and_destructuring; | |
2693 | duplicatedArg = true; | |
2694 | #endif | |
2695 | const char *name = js_AtomToPrintableString(cx, atom); | |
2696 | if (!name || | |
2697 | !js_ReportCompileErrorNumber(cx, TS(funtc.compiler), | |
2698 | NULL, | |
2699 | JSREPORT_WARNING | | |
2700 | JSREPORT_STRICT, | |
2701 | JSMSG_DUPLICATE_FORMAL, | |
2702 | name)) { | |
2703 | return NULL; | |
2704 | } | |
2705 | } | |
2706 | if (!DefineArg(pn, atom, fun->nargs, &funtc)) | |
2707 | return NULL; | |
2708 | if (!js_AddLocal(cx, fun, atom, JSLOCAL_ARG)) | |
2709 | return NULL; | return NULL; |
2710 | break; | break; |
2711 | } | |
2712 | ||
2713 | default: | default: |
2714 | js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, | js_ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, |
2715 | JSMSG_MISSING_FORMAL); | JSMSG_MISSING_FORMAL); |
2716 | /* FALL THROUGH */ | |
2717 | case TOK_ERROR: | |
2718 | return NULL; | return NULL; |
2719 | ||
2720 | #if JS_HAS_DESTRUCTURING | |
2721 | report_dup_and_destructuring: | |
2722 | js_ReportCompileErrorNumber(cx, TS(tc->compiler), NULL, | |
2723 | JSREPORT_ERROR, | |
2724 | JSMSG_DESTRUCT_DUP_ARG); | |
2725 | return NULL; | |
2726 | #endif | |
2727 | } | } |
2728 | } while (js_MatchToken(cx, ts, TOK_COMMA)); | } while (js_MatchToken(cx, ts, TOK_COMMA)); |
2729 | ||
# | Line 1290 | Line 2741 |
2741 | #else | #else |
2742 | MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_BODY); | MUST_MATCH_TOKEN(TOK_LC, JSMSG_CURLY_BEFORE_BODY); |
2743 | #endif | #endif |
pn->pn_pos.begin = CURRENT_TOKEN(ts).pos.begin; | ||
2744 | ||
2745 | body = FunctionBody(cx, ts, &funtc); | body = FunctionBody(cx, ts, &funtc); |
2746 | if (!body) | if (!body) |
# | Line 1299 | Line 2749 |
2749 | #if JS_HAS_EXPR_CLOSURES | #if JS_HAS_EXPR_CLOSURES |
2750 | if (tt == TOK_LC) | if (tt == TOK_LC) |
2751 | MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_BODY); | MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_BODY); |
2752 | else if (lambda == 0) | else if (lambda == 0 && !MatchOrInsertSemicolon(cx, ts)) |
2753 | js_MatchToken(cx, ts, TOK_SEMI); | return NULL; |
2754 | #else | #else |
2755 | MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_BODY); | MUST_MATCH_TOKEN(TOK_RC, JSMSG_CURLY_AFTER_BODY); |
2756 | #endif | #endif |
# | Line 1318 | Line 2768 |
2768 | if (body->pn_arity != PN_LIST) { | if (body->pn_arity != PN_LIST) { |
2769 | JSParseNode *block; | JSParseNode *block; |
2770 | ||
2771 | block = NewParseNode(cx, ts, PN_LIST, tc); | block = NewParseNode(PN_LIST, tc); |
2772 | if (!block) | if (!block) |
2773 | return NULL; | return NULL; |
2774 | block->pn_type = TOK_SEQ; | block->pn_type = TOK_SEQ; |
2775 | block->pn_pos = body->pn_pos; | block->pn_pos = body->pn_pos; |
2776 | PN_INIT_LIST_1(block, body); | block->initList(body); |
2777 | ||
2778 | body = block; | body = block; |
2779 | } | } |
2780 | ||
2781 | item = NewParseNode(cx, ts, PN_UNARY, tc); | item = NewParseNode(PN_UNARY, tc); |
2782 | if (!item) | if (!item) |
2783 | return NULL; | return NULL; |
2784 | ||
# | Line 1340 | Line 2790 |
2790 | if (body->pn_tail == &body->pn_head) | if (body->pn_tail == &body->pn_head) |
2791 | body->pn_tail = &item->pn_next; | body->pn_tail = &item->pn_next; |
2792 | ++body->pn_count; | ++body->pn_count; |
2793 | body->pn_xflags |= PNX_DESTRUCT; | |
2794 | } | } |
2795 | #endif | #endif |
2796 | ||
2797 | /* | /* |
2798 | * If we collected flags that indicate nested heavyweight functions, or | * If we collected flags that indicate nested heavyweight functions, or |
2799 | * this function contains heavyweight-making statements (references to | * this function contains heavyweight-making statements (with statement, |
2800 | * __parent__ or __proto__; use of with, or eval; and assignment to | * visible eval call, or assignment to 'arguments'), flag the function as |
2801 | * arguments), flag the function as heavyweight (requiring a call object | * heavyweight (requiring a call object per invocation). |
* per invocation). | ||
2802 | */ | */ |
2803 | if (funtc.flags & TCF_FUN_HEAVYWEIGHT) { | if (funtc.flags & TCF_FUN_HEAVYWEIGHT) { |
2804 | fun->flags |= JSFUN_HEAVYWEIGHT; | fun->flags |= JSFUN_HEAVYWEIGHT; |
# | Line 1356 | Line 2806 |
2806 | } else { | } else { |
2807 | /* | /* |
2808 | * If this function is a named statement function not at top-level | * If this function is a named statement function not at top-level |
2809 | * (i.e. not a top-level function definiton or expression), then | * (i.e. not a top-level function definiton or expression), then our |
2810 | * our enclosing function, if any, must be heavyweight. | * enclosing function, if any, must be heavyweight. |
* | ||
* The TCF_FUN_USES_NONLOCALS flag is set only by the code generator, | ||
* so it won't be set here. Assert that it's not. We have to check | ||
* it later, in js_EmitTree, after js_EmitFunctionScript has traversed | ||
* the function's body. | ||
2811 | */ | */ |
2812 | JS_ASSERT(!(funtc.flags & TCF_FUN_USES_NONLOCALS)); | if (!topLevel && lambda == 0 && funAtom) |
if (lambda == 0 && funAtom && !AT_TOP_LEVEL(tc)) | ||
2813 | tc->flags |= TCF_FUN_HEAVYWEIGHT; | tc->flags |= TCF_FUN_HEAVYWEIGHT; |
2814 | } | } |
2815 | ||
# | Line 1374 | Line 2818 |
2818 | /* | /* |
2819 | * ECMA ed. 3 standard: function expression, possibly anonymous. | * ECMA ed. 3 standard: function expression, possibly anonymous. |
2820 | */ | */ |
2821 | op = funAtom ? JSOP_NAMEDFUNOBJ : JSOP_ANONFUNOBJ; | op = JSOP_LAMBDA; |
2822 | } else if (!funAtom) { | } else if (!funAtom) { |
2823 | /* | /* |
2824 | * If this anonymous function definition is *not* embedded within a | * If this anonymous function definition is *not* embedded within a |
# | Line 1383 | Line 2827 |
2827 | * Edition 3 would have it). Backward compatibility must trump all, | * Edition 3 would have it). Backward compatibility must trump all, |
2828 | * unless JSOPTION_ANONFUNFIX is set. | * unless JSOPTION_ANONFUNFIX is set. |
2829 | */ | */ |
2830 | result = NewParseNode(cx, ts, PN_UNARY, tc); | result = NewParseNode(PN_UNARY, tc); |
2831 | if (!result) | if (!result) |
2832 | return NULL; | return NULL; |
2833 | result->pn_type = TOK_SEMI; | result->pn_type = TOK_SEMI; |
2834 | result->pn_pos = pn->pn_pos; | result->pn_pos = pn->pn_pos; |
2835 | result->pn_kid = pn; | result->pn_kid = pn; |
2836 | op = JSOP_ANONFUNOBJ; | op = JSOP_LAMBDA; |
2837 | } else if (!AT_TOP_LEVEL(tc)) { | } else if (!topLevel) { |
2838 | /* | /* |
2839 | * ECMA ed. 3 extension: a function expression statement not at the | * ECMA ed. 3 extension: a function expression statement not at the |
2840 | * top level, e.g., in a compound statement such as the "then" part | * top level, e.g., in a compound statement such as the "then" part |
# | Line 1402 | Line 2846 |
2846 | op = JSOP_NOP; | op = JSOP_NOP; |
2847 | } | } |
2848 | ||
2849 | pn->pn_funpob = funpob; | funbox->kids = funtc.functionList; |
2850 | ||
2851 | pn->pn_funbox = funbox; | |
2852 | pn->pn_op = op; | pn->pn_op = op; |
2853 | pn->pn_body = body; | if (pn->pn_body) { |
2854 | pn->pn_flags = funtc.flags & (TCF_FUN_FLAGS | TCF_HAS_DEFXMLNS | TCF_COMPILE_N_GO); | pn->pn_body->append(body); |
2855 | TREE_CONTEXT_FINISH(cx, &funtc); | pn->pn_body->pn_pos = body->pn_pos; |
2856 | } else { | |
2857 | pn->pn_body = body; | |
2858 | } | |
2859 | ||
2860 | pn->pn_blockid = tc->blockid(); | |
2861 | ||
2862 | if (!LeaveFunction(pn, &funtc, tc, funAtom, lambda)) | |
2863 | return NULL; | |
2864 | ||
2865 | return result; | return result; |
2866 | } | } |
2867 | ||
# | Line 1435 | Line 2890 |
2890 | ||
2891 | JS_CHECK_RECURSION(cx, return NULL); | JS_CHECK_RECURSION(cx, return NULL); |
2892 | ||
2893 | pn = NewParseNode(cx, ts, PN_LIST, tc); | pn = NewParseNode(PN_LIST, tc); |
2894 | if (!pn) | if (!pn) |
2895 | return NULL; | return NULL; |
2896 | pn->pn_type = TOK_LC; | |
2897 | pn->makeEmpty(); | |
2898 | pn->pn_blockid = tc->blockid(); | |
2899 | saveBlock = tc->blockNode; | saveBlock = tc->blockNode; |
2900 | tc->blockNode = pn; | tc->blockNode = pn; |
PN_INIT_LIST(pn); | ||
2901 | ||
2902 | for (;;) { | for (;;) { |
2903 | ts->flags |= TSF_OPERAND; | ts->flags |= TSF_OPERAND; |
2904 | tt = js_PeekToken(cx, ts); | tt = js_PeekToken(cx, ts); |
2905 | ts->flags &= ~TSF_OPERAND; | ts->flags &= ~TSF_OPERAND; |
2906 | if (tt <= TOK_EOF || tt == TOK_RC) { | if (tt <= TOK_EOF || tt == TOK_RC) { |
2907 | if (tt == TOK_ERROR) | if (tt == TOK_ERROR) { |
2908 | if (ts->flags & TSF_EOF) | |
2909 | ts->flags |= TSF_UNEXPECTED_EOF; | |
2910 | return NULL; | return NULL; |
2911 | } | |
2912 | break; | break; |
2913 | } | } |
2914 | pn2 = Statement(cx, ts, tc); | pn2 = Statement(cx, ts, tc); |
# | Line 1468 | Line 2928 |
2928 | * is relevant only for function definitions not at top-level, | * is relevant only for function definitions not at top-level, |
2929 | * which we call function statements. | * which we call function statements. |
2930 | */ | */ |
2931 | if (AT_TOP_LEVEL(tc)) | if (tc->atTopLevel()) |
2932 | pn->pn_extra |= PNX_FUNCDEFS; | pn->pn_xflags |= PNX_FUNCDEFS; |
2933 | else | else |
2934 | tc->flags |= TCF_HAS_FUNCTION_STMT; | tc->flags |= TCF_HAS_FUNCTION_STMT; |
2935 | } | } |
2936 | PN_APPEND(pn, pn2); | pn->append(pn2); |
2937 | } | } |
2938 | ||
2939 | /* | /* |
# | Line 1540 | Line 3000 |
3000 | static JSBool | static JSBool |
3001 | BindLet(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc) | BindLet(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc) |
3002 | { | { |
3003 | JSParseNode *pn; | |
3004 | JSObject *blockObj; | JSObject *blockObj; |
JSScopeProperty *sprop; | ||
3005 | JSAtomListElement *ale; | JSAtomListElement *ale; |
3006 | uintN n; | jsint n; |
3007 | ||
3008 | blockObj = tc->blockChain; | /* |
3009 | sprop = SCOPE_GET_PROPERTY(OBJ_SCOPE(blockObj), ATOM_TO_JSID(atom)); | * Top-level 'let' is the same as 'var' currently -- this may change in a |
3010 | ATOM_LIST_SEARCH(ale, &tc->decls, atom); | * successor standard to ES3.1 that specifies 'let'. |
3011 | if (sprop || (ale && ALE_JSOP(ale) == JSOP_DEFCONST)) { | */ |
3012 | const char *name; | JS_ASSERT(!tc->atTopLevel()); |
if (sprop) { | ||
JS_ASSERT(sprop->flags & SPROP_HAS_SHORTID); | ||
JS_ASSERT((uint16)sprop->shortid < OBJ_BLOCK_COUNT(cx, blockObj)); | ||
} | ||
3013 | ||
3014 | name = js_AtomToPrintableString(cx, atom); | pn = data->pn; |
3015 | blockObj = tc->blockChain; | |
3016 | ale = tc->decls.lookup(atom); | |
3017 | if (ale && ALE_DEFN(ale)->pn_blockid == tc->blockid()) { | |
3018 | const char *name = js_AtomToPrintableString(cx, atom); | |
3019 | if (name) { | if (name) { |
3020 | js_ReportCompileErrorNumber(cx, TS(tc->parseContext), data->pn, | js_ReportCompileErrorNumber(cx, TS(tc->compiler), pn, |
3021 | JSREPORT_ERROR, JSMSG_REDECLARED_VAR, | JSREPORT_ERROR, JSMSG_REDECLARED_VAR, |
3022 | (ale && ALE_JSOP(ale) == JSOP_DEFCONST) | (ale && ALE_DEFN(ale)->isConst()) |
3023 | ? js_const_str | ? js_const_str |
3024 | : "variable", | : js_variable_str, |
3025 | name); | name); |
3026 | } | } |
3027 | return JS_FALSE; | return JS_FALSE; |
# | Line 1570 | Line 3029 |
3029 | ||
3030 | n = OBJ_BLOCK_COUNT(cx, blockObj); | n = OBJ_BLOCK_COUNT(cx, blockObj); |
3031 | if (n == JS_BIT(16)) { | if (n == JS_BIT(16)) { |
3032 | js_ReportCompileErrorNumber(cx, TS(tc->parseContext), data->pn, | js_ReportCompileErrorNumber(cx, TS(tc->compiler), pn, |
3033 | JSREPORT_ERROR, data->u.let.overflow); | JSREPORT_ERROR, data->let.overflow); |
3034 | return JS_FALSE; | return JS_FALSE; |
3035 | } | } |
3036 | ||
3037 | /* Use JSPROP_ENUMERATE to aid the disassembler. */ | /* |
3038 | return js_DefineNativeProperty(cx, blockObj, ATOM_TO_JSID(atom), | * Pass push = true to Define so it pushes an ale ahead of any outer scope. |
3039 | JSVAL_VOID, NULL, NULL, | * This is balanced by PopStatement, defined immediately below. |
3040 | JSPROP_ENUMERATE | | */ |
3041 | JSPROP_PERMANENT | | if (!Define(pn, atom, tc, true)) |
3042 | JSPROP_SHARED, | return JS_FALSE; |
3043 | SPROP_HAS_SHORTID, (int16) n, NULL); | |
3044 | /* | |
3045 | * Assign block-local index to pn->pn_cookie right away, encoding it as an | |
3046 | * upvar cookie whose skip tells the current static level. The emitter will | |
3047 | * adjust the node's slot based on its stack depth model -- and, for global | |
3048 | * and eval code, JSCompiler::compileScript will adjust the slot again to | |
3049 | * include script->nfixed. | |
3050 | */ | |
3051 | pn->pn_op = JSOP_GETLOCAL; | |
3052 | pn->pn_cookie = MAKE_UPVAR_COOKIE(tc->staticLevel, n); | |
3053 | pn->pn_dflags |= PND_LET | PND_BOUND; | |
3054 | ||
3055 | /* | |
3056 | * Use JSPROP_ENUMERATE to aid the disassembler. Define the let binding's | |
3057 | * property before storing pn in a reserved slot, since block_reserveSlots | |
3058 | * depends on OBJ_SCOPE(blockObj)->entryCount. | |
3059 | */ | |
3060 | if (!js_DefineNativeProperty(cx, blockObj, ATOM_TO_JSID(atom), JSVAL_VOID, | |
3061 | NULL, NULL, | |
3062 | JSPROP_ENUMERATE | | |
3063 | JSPROP_PERMANENT | | |
3064 | JSPROP_SHARED, | |
3065 | SPROP_HAS_SHORTID, (int16) n, NULL)) { | |
3066 | return JS_FALSE; | |
3067 | } | |
3068 | ||
3069 | /* | |
3070 | * Store pn temporarily in what would be reserved slots in a cloned block | |
3071 | * object (once the prototype's final population is known, after all 'let' | |
3072 | * bindings for this block have been parsed). We will free these reserved | |
3073 | * slots in jsemit.cpp:EmitEnterBlock. | |
3074 | */ | |
3075 | uintN slot = JSSLOT_FREE(&js_BlockClass) + n; | |
3076 | if (slot >= STOBJ_NSLOTS(blockObj) && | |
3077 | !js_ReallocSlots(cx, blockObj, slot + 1, JS_FALSE)) { | |
3078 | return JS_FALSE; | |
3079 | } | |
3080 | OBJ_SCOPE(blockObj)->freeslot = slot + 1; | |
3081 | STOBJ_SET_SLOT(blockObj, slot, PRIVATE_TO_JSVAL(pn)); | |
3082 | return JS_TRUE; | |
3083 | } | |
3084 | ||
3085 | static void | |
3086 | PopStatement(JSTreeContext *tc) | |
3087 | { | |
3088 | JSStmtInfo *stmt = tc->topStmt; | |
3089 | ||
3090 | if (stmt->flags & SIF_SCOPE) { | |
3091 | JSObject *obj = stmt->blockObj; | |
3092 | JSScope *scope = OBJ_SCOPE(obj); | |
3093 | JS_ASSERT(scope->object == obj); | |
3094 | ||
3095 | for (JSScopeProperty *sprop = scope->lastProp; sprop; sprop = sprop->parent) { | |
3096 | JSAtom *atom = JSID_TO_ATOM(sprop->id); | |
3097 | ||
3098 | /* Beware the empty destructuring dummy. */ | |
3099 | if (atom == tc->compiler->context->runtime->atomState.emptyAtom) | |
3100 | continue; | |
3101 | tc->decls.remove(tc->compiler, atom); | |
3102 | } | |
3103 | } | |
3104 | js_PopStatement(tc); | |
3105 | } | |
3106 | ||
3107 | static inline bool | |
3108 | OuterLet(JSTreeContext *tc, JSStmtInfo *stmt, JSAtom *atom) | |
3109 | { | |
3110 | while (stmt->downScope) { | |
3111 | stmt = js_LexicalLookup(tc, atom, NULL, stmt->downScope); | |
3112 | if (!stmt) | |
3113 | return false; | |
3114 | if (stmt->type == STMT_BLOCK) | |
3115 | return true; | |
3116 | } | |
3117 | return false; | |
3118 | } | } |
3119 | ||
3120 | static JSBool | static JSBool |
3121 | BindVarOrConst(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc) | BindVarOrConst(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc) |
3122 | { | { |
3123 | JSStmtInfo *stmt; | JSStmtInfo *stmt = js_LexicalLookup(tc, atom, NULL); |
3124 | JSAtomListElement *ale; | JSParseNode *pn = data->pn; |
3125 | JSOp op, prevop; | |
3126 | const char *name; | if (stmt && stmt->type == STMT_WITH) { |
3127 | JSLocalKind localKind; | pn->pn_op = JSOP_NAME; |
3128 | data->fresh = false; | |
3129 | return JS_TRUE; | |
3130 | } | |
3131 | ||
3132 | stmt = js_LexicalLookup(tc, atom, NULL); | JSAtomListElement *ale = tc->decls.lookup(atom); |
3133 | ATOM_LIST_SEARCH(ale, &tc->decls, atom); | JSOp op = data->op; |
3134 | op = data->op; | |
3135 | if ((stmt && stmt->type != STMT_WITH) || ale) { | if (stmt || ale) { |
3136 | prevop = ale ? ALE_JSOP(ale) : JSOP_DEFVAR; | JSDefinition *dn = ale ? ALE_DEFN(ale) : NULL; |
3137 | if (JS_HAS_STRICT_OPTION(cx) | JSDefinition::Kind dn_kind = dn ? dn->kind() : JSDefinition::VAR; |
3138 | ? op != JSOP_DEFVAR || prevop != JSOP_DEFVAR | const char *name; |
3139 | : op == JSOP_DEFCONST || prevop == JSOP_DEFCONST) { | |
3140 | if (dn_kind == JSDefinition::ARG) { | |
3141 | name = js_AtomToPrintableString(cx, atom); | name = js_AtomToPrintableString(cx, atom); |
3142 | if (!name || | if (!name) |
3143 | !js_ReportCompileErrorNumber(cx, TS(tc->parseContext), data->pn, | return JS_FALSE; |
3144 | (op != JSOP_DEFCONST && | |
3145 | prevop != JSOP_DEFCONST) | if (op == JSOP_DEFCONST) { |
3146 | ? JSREPORT_WARNING | | js_ReportCompileErrorNumber(cx, TS(tc->compiler), pn, |
3147 | JSREPORT_STRICT | JSREPORT_ERROR, JSMSG_REDECLARED_PARAM, |
3148 | : JSREPORT_ERROR, | name); |
JSMSG_REDECLARED_VAR, | ||
(prevop == JSOP_DEFFUN) | ||
? js_function_str | ||
: (prevop == JSOP_DEFCONST) | ||
? js_const_str | ||
: js_var_str, | ||
name)) { | ||
3149 | return JS_FALSE; | return JS_FALSE; |
3150 | } | } |
3151 | if (!js_ReportCompileErrorNumber(cx, TS(tc->compiler), pn, | |
3152 | JSREPORT_WARNING | JSREPORT_STRICT, | |
3153 | JSMSG_VAR_HIDES_ARG, name)) { | |
3154 | return JS_FALSE; | |
3155 | } | |
3156 | } else { | |
3157 | bool error = (op == JSOP_DEFCONST || | |
3158 | dn_kind == JSDefinition::CONST || | |
3159 | (dn_kind == JSDefinition::LET && | |
3160 | (stmt->type != STMT_CATCH || OuterLet(tc, stmt, atom)))); | |
3161 | ||
3162 | if (JS_HAS_STRICT_OPTION(cx) | |
3163 | ? op != JSOP_DEFVAR || dn_kind != JSDefinition::VAR | |
3164 | : error) { | |
3165 | name = js_AtomToPrintableString(cx, atom); | |
3166 | if (!name || | |
3167 | !js_ReportCompileErrorNumber(cx, TS(tc->compiler), pn, | |
3168 | !error | |
3169 | ? JSREPORT_WARNING | JSREPORT_STRICT | |
3170 | : JSREPORT_ERROR, | |
3171 | JSMSG_REDECLARED_VAR, | |
3172 | JSDefinition::kindString(dn_kind), | |
3173 | name)) { | |
3174 | return JS_FALSE; | |
3175 | } | |
3176 | } | |
3177 | } | } |
if (op == JSOP_DEFVAR && prevop == JSOP_DEFFUN) | ||
tc->flags |= TCF_FUN_CLOSURE_VS_VAR; | ||
3178 | } | } |
3179 | ||
3180 | if (!ale) { | if (!ale) { |
3181 | ale = js_IndexAtom(cx, atom, &tc->decls); | if (!Define(pn, atom, tc)) |
3182 | return JS_FALSE; | |
3183 | } else { | |
3184 | /* | |
3185 | * A var declaration never recreates an existing binding, it restates | |
3186 | * it and possibly reinitializes its value. Beware that if pn becomes a | |
3187 | * use of ALE_DEFN(ale), and if we have an initializer for this var or | |
3188 | * const (typically a const would ;-), then pn must be rewritten into a | |
3189 | * TOK_ASSIGN node. See Variables, further below. | |
3190 | * | |
3191 | * A case such as let (x = 1) { var x = 2; print(x); } is even harder. | |
3192 | * There the x definition is hoisted but the x = 2 assignment mutates | |
3193 | * the block-local binding of x. | |
3194 | */ | |
3195 | JSDefinition *dn = ALE_DEFN(ale); | |
3196 | ||
3197 | data->fresh = false; | |
3198 | ||
3199 | if (!pn->pn_used) { | |
3200 | /* Make pnu be a fresh name node that uses dn. */ | |
3201 | JSParseNode *pnu = pn; | |
3202 | ||
3203 | if (pn->pn_defn) { | |
3204 | pnu = NewNameNode(cx, TS(tc->compiler), atom, tc); | |
3205 | if (!pnu) | |
3206 | return JS_FALSE; | |
3207 | } | |
3208 | ||
3209 | LinkUseToDef(pnu, dn, tc); | |
3210 | pnu->pn_op = JSOP_NAME; | |
3211 | } | |
3212 | ||
3213 | while (dn->kind() == JSDefinition::LET) { | |
3214 | do { | |
3215 | ale = ALE_NEXT(ale); | |
3216 | } while (ale && ALE_ATOM(ale) != atom); | |
3217 | if (!ale) | |
3218 | break; | |
3219 | dn = ALE_DEFN(ale); | |
3220 | } | |
3221 | ||
3222 | if (ale) { | |
3223 | JS_ASSERT_IF(data->op == JSOP_DEFCONST, | |
3224 | dn->kind() == JSDefinition::CONST); | |
3225 | return JS_TRUE; | |
3226 | } | |
3227 | ||
3228 | /* | |
3229 | * A var or const that is shadowed by one or more let bindings of the | |
3230 | * same name, but that has not been declared until this point, must be | |
3231 | * hoisted above the let bindings. | |
3232 | */ | |
3233 | if (!pn->pn_defn) { | |
3234 | JSHashEntry **hep; | |
3235 | ||
3236 | ale = tc->lexdeps.rawLookup(atom, hep); | |
3237 | if (ale) { | |
3238 | pn = ALE_DEFN(ale); | |
3239 | tc->lexdeps.rawRemove(tc->compiler, ale, hep); | |
3240 | } else { | |
3241 | JSParseNode *pn2 = NewNameNode(cx, TS(tc->compiler), atom, tc); | |
3242 | if (!pn2) | |
3243 | return JS_FALSE; | |
3244 | ||
3245 | /* The token stream may be past the location for pn. */ | |
3246 | pn2->pn_type = TOK_NAME; | |
3247 | pn2->pn_pos = pn->pn_pos; | |
3248 | pn = pn2; | |
3249 | } | |
3250 | pn->pn_op = JSOP_NAME; | |
3251 | } | |
3252 | ||
3253 | ale = tc->decls.add(tc->compiler, atom, JSAtomList::HOIST); | |
3254 | if (!ale) | if (!ale) |
3255 | return JS_FALSE; | return JS_FALSE; |
3256 | ALE_SET_DEFN(ale, pn); | |
3257 | pn->pn_defn = true; | |
3258 | pn->pn_dflags &= ~PND_PLACEHOLDER; | |
3259 | } | } |
3260 | ALE_SET_JSOP(ale, op); | |
3261 | if (data->op == JSOP_DEFCONST) | |
3262 | pn->pn_dflags |= PND_CONST; | |
3263 | ||
3264 | if (!(tc->flags & TCF_IN_FUNCTION)) { | if (!(tc->flags & TCF_IN_FUNCTION)) { |
3265 | /* | /* |
3266 | * Don't lookup global variables or variables in an active frame at | * If we are generating global or eval-called-from-global code, bind a |
3267 | * compile time. | * "gvar" here, as soon as possible. The JSOP_GETGVAR, etc., ops speed |
3268 | * up global variable access by memoizing name-to-slot mappings in the | |
3269 | * script prolog (via JSOP_DEFVAR/JSOP_DEFCONST). If the memoization | |
3270 | * can't be done due to a pre-existing property of the same name as the | |
3271 | * var or const but incompatible attributes/getter/setter/etc, these | |
3272 | * ops devolve to JSOP_NAME, etc. | |
3273 | * | |
3274 | * For now, don't try to lookup eval frame variables at compile time. | |
3275 | * Seems sub-optimal: why couldn't we find eval-called-from-a-function | |
3276 | * upvars early and possibly simplify jsemit.cpp:BindNameToSlot? | |
3277 | */ | */ |
3278 | pn->pn_op = JSOP_NAME; | |
3279 | if ((tc->flags & TCF_COMPILING) && !tc->compiler->callerFrame) { | |
3280 | JSCodeGenerator *cg = (JSCodeGenerator *) tc; | |
3281 | ||
3282 | /* Index atom so we can map fast global number to name. */ | |
3283 | ale = cg->atomList.add(tc->compiler, atom); | |
3284 | if (!ale) | |
3285 | return JS_FALSE; | |
3286 | ||
3287 | /* Defend against cg->ngvars 16-bit overflow. */ | |
3288 | uintN slot = ALE_INDEX(ale); | |
3289 | if ((slot + 1) >> 16) | |
3290 | return JS_TRUE; | |
3291 | ||
3292 | if ((uint16)(slot + 1) > cg->ngvars) | |
3293 | cg->ngvars = (uint16)(slot + 1); | |
3294 | ||
3295 | pn->pn_op = JSOP_GETGVAR; | |
3296 | pn->pn_cookie = MAKE_UPVAR_COOKIE(tc->staticLevel, slot); | |
3297 | pn->pn_dflags |= PND_BOUND | PND_GVAR; | |
3298 | } | |
3299 | return JS_TRUE; | |
3300 | } | |
3301 | ||
3302 | if (atom == cx->runtime->atomState.argumentsAtom) { | |
3303 | pn->pn_op = JSOP_ARGUMENTS; | |
3304 | pn->pn_dflags |= PND_BOUND; | |
3305 | return JS_TRUE; | return JS_TRUE; |
3306 | } | } |
3307 | ||
3308 | localKind = js_LookupLocal(cx, tc->u.fun, atom, NULL); | JSLocalKind localKind = js_LookupLocal(cx, tc->fun, atom, NULL); |
3309 | if (localKind == JSLOCAL_NONE) { | if (localKind == JSLOCAL_NONE) { |
3310 | /* | /* |
3311 | * Property not found in current variable scope: we have not seen this | * Property not found in current variable scope: we have not seen this |
3312 | * variable before. Define a new local variable by adding a property | * variable before. Define a new local variable by adding a property to |
3313 | * to the function's scope, allocating one slot in the function's vars | * the function's scope and allocating one slot in the function's vars |
3314 | * frame. Any locals declared in with statement bodies are handled at | * frame. Any locals declared in a with statement body are handled at |
3315 | * runtime, by script prolog JSOP_DEFVAR opcodes generated for | * runtime, by script prolog JSOP_DEFVAR opcodes generated for global |
3316 | * slot-less vars. | * and heavyweight-function-local vars. |
3317 | */ | */ |
3318 | localKind = (data->op == JSOP_DEFCONST) ? JSLOCAL_CONST : JSLOCAL_VAR; | localKind = (data->op == JSOP_DEFCONST) ? JSLOCAL_CONST : JSLOCAL_VAR; |
if (!js_InWithStatement(tc) && | ||
!BindLocalVariable(cx, tc->u.fun, atom, localKind)) { | ||
return JS_FALSE; | ||
} | ||
} else if (localKind == JSLOCAL_ARG) { | ||
name = js_AtomToPrintableString(cx, atom); | ||
if (!name) | ||
return JS_FALSE; | ||
3319 | ||
3320 | if (op == JSOP_DEFCONST) { | uintN index = tc->fun->u.i.nvars; |
3321 | js_ReportCompileErrorNumber(cx, TS(tc->parseContext), data->pn, | if (!BindLocalVariable(cx, tc->fun, atom, localKind)) |
JSREPORT_ERROR, JSMSG_REDECLARED_PARAM, | ||
name); | ||
3322 | return JS_FALSE; | return JS_FALSE; |
3323 | } | pn->pn_op = JSOP_GETLOCAL; |
3324 | if (!js_ReportCompileErrorNumber(cx, TS(tc->parseContext), data->pn, | pn->pn_cookie = MAKE_UPVAR_COOKIE(tc->staticLevel, index); |
3325 | JSREPORT_WARNING | JSREPORT_STRICT, | pn->pn_dflags |= PND_BOUND; |
3326 | JSMSG_VAR_HIDES_ARG, name)) { | return JS_TRUE; |
3327 | return JS_FALSE; | } |
3328 | } | |
3329 | if (localKind == JSLOCAL_ARG) { | |
3330 | /* We checked errors and strict warnings earlier -- see above. */ | |
3331 | JS_ASSERT(ale && ALE_DEFN(ale)->kind() == JSDefinition::ARG); | |
3332 | } else { | } else { |
3333 | /* Not an argument, must be a redeclared local var. */ | /* Not an argument, must be a redeclared local var. */ |
3334 | JS_ASSERT(localKind == JSLOCAL_VAR || localKind == JSLOCAL_CONST); | JS_ASSERT(localKind == JSLOCAL_VAR || localKind == JSLOCAL_CONST); |
3335 | } | } |
3336 | pn->pn_op = JSOP_NAME; | |
3337 | return JS_TRUE; | return JS_TRUE; |
3338 | } | } |
3339 | ||
# | Line 1683 | Line 3345 |
3345 | JS_ASSERT(pn->pn_arity == PN_LIST); | JS_ASSERT(pn->pn_arity == PN_LIST); |
3346 | JS_ASSERT(pn->pn_op == JSOP_CALL || pn->pn_op == JSOP_EVAL || pn->pn_op == JSOP_APPLY); | JS_ASSERT(pn->pn_op == JSOP_CALL || pn->pn_op == JSOP_EVAL || pn->pn_op == JSOP_APPLY); |
3347 | pn2 = pn->pn_head; | pn2 = pn->pn_head; |
3348 | if (pn2->pn_type == TOK_FUNCTION && (pn2->pn_flags & TCF_GENEXP_LAMBDA)) { | if (pn2->pn_type == TOK_FUNCTION && (pn2->pn_funbox->tcflags & TCF_GENEXP_LAMBDA)) { |
3349 | js_ReportCompileErrorNumber(cx, TS(tc->parseContext), pn, | js_ReportCompileErrorNumber(cx, TS(tc->compiler), pn, JSREPORT_ERROR, msg); |
JSREPORT_ERROR, msg); | ||
3350 | return JS_FALSE; | return JS_FALSE; |
3351 | } | } |
3352 | pn->pn_op = JSOP_SETCALL; | pn->pn_op = JSOP_SETCALL; |
3353 | return JS_TRUE; | return JS_TRUE; |
3354 | } | } |
3355 | ||
3356 | static void | |
3357 | NoteLValue(JSContext *cx, JSParseNode *pn, JSTreeContext *tc, uintN dflag = PND_ASSIGNED) | |
3358 | { | |
3359 | if (pn->pn_used) { | |
3360 | JSDefinition *dn = pn->pn_lexdef; | |
3361 | ||
3362 | /* | |
3363 | * Save the win of PND_INITIALIZED if we can prove 'var x;' and 'x = y' | |
3364 | * occur as direct kids of the same block with no forward refs to x. | |
3365 | */ | |
3366 | if (!(dn->pn_dflags & (PND_INITIALIZED | PND_CONST | PND_PLACEHOLDER)) && | |
3367 | dn->isBlockChild() && | |
3368 | pn->isBlockChild() && | |
3369 | dn->pn_blockid == pn->pn_blockid && | |
3370 | dn->pn_pos.end <= pn->pn_pos.begin && | |
3371 | dn->dn_uses == pn) { | |
3372 | dflag = PND_INITIALIZED; | |
3373 | } | |
3374 | ||
3375 | dn->pn_dflags |= dflag; | |
3376 | ||
3377 | if (dn->frameLevel() != tc->staticLevel) { | |
3378 | /* | |
3379 | * The above condition takes advantage of the all-ones nature of | |
3380 | * FREE_UPVAR_COOKIE, and the reserved level FREE_STATIC_LEVEL. | |
3381 | * We make a stronger assertion by excluding FREE_UPVAR_COOKIE. | |
3382 | */ | |
3383 | JS_ASSERT_IF(dn->pn_cookie != FREE_UPVAR_COOKIE, | |
3384 | dn->frameLevel() < tc->staticLevel); | |
3385 | tc->flags |= TCF_FUN_SETS_OUTER_NAME; | |
3386 | } | |
3387 | } | |
3388 | ||
3389 | pn->pn_dflags |= dflag; | |
3390 | ||
3391 | if (pn->pn_atom == cx->runtime->atomState.argumentsAtom) | |
3392 | tc->flags |= TCF_FUN_HEAVYWEIGHT; | |
3393 | } | |
3394 | ||
3395 | #if JS_HAS_DESTRUCTURING | #if JS_HAS_DESTRUCTURING |
3396 | ||
3397 | static JSBool | static JSBool |
# | Line 1713 | Line 3413 |
3413 | data->pn = pn; | data->pn = pn; |
3414 | if (!data->binder(cx, data, atom, tc)) | if (!data->binder(cx, data, atom, tc)) |
3415 | return JS_FALSE; | return JS_FALSE; |
data->pn = NULL; | ||
3416 | ||
3417 | /* | /* |
3418 | * Select the appropriate name-setting opcode, which may be specialized | * Select the appropriate name-setting opcode, respecting eager selection |
3419 | * further for local variable and argument slot optimizations. At this | * done by the data->binder function. |
* point, we can't select the optimal final opcode, yet we must preserve | ||
* the CONST bit and convey "set", not "get". | ||
3420 | */ | */ |
3421 | if (data->op == JSOP_DEFCONST) { | if (pn->pn_dflags & PND_BOUND) { |
3422 | pn->pn_op = JSOP_SETCONST; | pn->pn_op = (pn->pn_op == JSOP_ARGUMENTS) |
3423 | pn->pn_const = JS_TRUE; | ? JSOP_SETNAME |
3424 | : (pn->pn_dflags & PND_GVAR) | |
3425 | ? JSOP_SETGVAR | |
3426 | : JSOP_SETLOCAL; | |
3427 | } else { | } else { |
3428 | pn->pn_op = JSOP_SETNAME; | pn->pn_op = (data->op == JSOP_DEFCONST) |
3429 | pn->pn_const = JS_FALSE; | ? JSOP_SETCONST |
3430 | : JSOP_SETNAME; | |
3431 | } | } |
3432 | ||
3433 | if (data->op == JSOP_DEFCONST) | |
3434 | pn->pn_dflags |= PND_CONST; | |
3435 | ||
3436 | NoteLValue(cx, pn, tc, PND_INITIALIZED); | |
3437 | return JS_TRUE; | return JS_TRUE; |
3438 | } | } |
3439 | ||
# | Line 1757 | Line 3463 |
3463 | ||
3464 | switch (pn->pn_type) { | switch (pn->pn_type) { |
3465 | case TOK_NAME: | case TOK_NAME: |
3466 | if (pn->pn_atom == cx->runtime->atomState.argumentsAtom) | NoteLValue(cx, pn, tc); |
tc->flags |= TCF_FUN_HEAVYWEIGHT; | ||
3467 | /* FALL THROUGH */ | /* FALL THROUGH */ |
3468 | ||
3469 | case TOK_DOT: | case TOK_DOT: |
3470 | case TOK_LB: | case TOK_LB: |
3471 | pn->pn_op = JSOP_SETNAME; | pn->pn_op = JSOP_SETNAME; |
# | Line 1782 | Line 3488 |
3488 | #endif | #endif |
3489 | ||
3490 | default: | default: |
3491 | js_ReportCompileErrorNumber(cx, TS(tc->parseContext), pn, | js_ReportCompileErrorNumber(cx, TS(tc->compiler), pn, |
3492 | JSREPORT_ERROR, JSMSG_BAD_LEFTSIDE_OF_ASS); | JSREPORT_ERROR, JSMSG_BAD_LEFTSIDE_OF_ASS); |
3493 | return JS_FALSE; | return JS_FALSE; |
3494 | } | } |
# | Line 1803 | Line 3509 |
3509 | } FindPropValEntry; | } FindPropValEntry; |
3510 | ||
3511 | #define ASSERT_VALID_PROPERTY_KEY(pnkey) \ | #define ASSERT_VALID_PROPERTY_KEY(pnkey) \ |
3512 | JS_ASSERT((pnkey)->pn_arity == PN_NULLARY && \ | JS_ASSERT(((pnkey)->pn_arity == PN_NULLARY && \ |
3513 | ((pnkey)->pn_type == TOK_NUMBER || \ | ((pnkey)->pn_type == TOK_NUMBER || \ |
3514 | (pnkey)->pn_type == TOK_STRING || \ | (pnkey)->pn_type == TOK_STRING || \ |
3515 | (pnkey)->pn_type == TOK_NAME)) | (pnkey)->pn_type == TOK_NAME)) || \ |
3516 | (pnkey)->pn_arity == PN_NAME && (pnkey)->pn_type == TOK_NAME) | |
3517 | ||
3518 | static JSDHashNumber | static JSDHashNumber |
3519 | HashFindPropValKey(JSDHashTable *table, const void *key) | HashFindPropValKey(JSDHashTable *table, const void *key) |
# | Line 1876 | Line 3583 |
3583 | step = 0; | step = 0; |
3584 | ASSERT_VALID_PROPERTY_KEY(pnid); | ASSERT_VALID_PROPERTY_KEY(pnid); |
3585 | pnhead = pn->pn_head; | pnhead = pn->pn_head; |
if (pnhead && pnhead->pn_type == TOK_DEFSHARP) | ||
pnhead = pnhead->pn_next; | ||
3586 | if (pnid->pn_type == TOK_NUMBER) { | if (pnid->pn_type == TOK_NUMBER) { |
3587 | for (pnprop = pnhead; pnprop; pnprop = pnprop->pn_next) { | for (pnprop = pnhead; pnprop; pnprop = pnprop->pn_next) { |
3588 | JS_ASSERT(pnprop->pn_type == TOK_COLON); | JS_ASSERT(pnprop->pn_type == TOK_COLON); |
# | Line 1936 | Line 3641 |
3641 | * If data is null, the caller is AssignExpr and instead of binding variables, | * If data is null, the caller is AssignExpr and instead of binding variables, |
3642 | * we specialize lvalues in the propery value positions of the left-hand side. | * we specialize lvalues in the propery value positions of the left-hand side. |
3643 | * If right is null, just check for well-formed lvalues. | * If right is null, just check for well-formed lvalues. |
3644 | * | |
3645 | * See also UndominateInitializers, immediately below. If you change either of | |
3646 | * these functions, you might have to change the other to match. | |
3647 | */ | */ |
3648 | static JSBool | static JSBool |
3649 | CheckDestructuring(JSContext *cx, BindData *data, | CheckDestructuring(JSContext *cx, BindData *data, |
# | Line 1947 | Line 3655 |
3655 | JSParseNode *lhs, *rhs, *pn, *pn2; | JSParseNode *lhs, *rhs, *pn, *pn2; |
3656 | ||
3657 | if (left->pn_type == TOK_ARRAYCOMP) { | if (left->pn_type == TOK_ARRAYCOMP) { |
3658 | js_ReportCompileErrorNumber(cx, TS(tc->parseContext), left, | js_ReportCompileErrorNumber(cx, TS(tc->compiler), left, |
3659 | JSREPORT_ERROR, JSMSG_ARRAY_COMP_LEFTSIDE); | JSREPORT_ERROR, JSMSG_ARRAY_COMP_LEFTSIDE); |
3660 | return JS_FALSE; | return JS_FALSE; |
3661 | } | } |
3662 | ||
3663 | #if JS_HAS_DESTRUCTURING_SHORTHAND | #if JS_HAS_DESTRUCTURING_SHORTHAND |
3664 | if (right && right->pn_arity == PN_LIST && (right->pn_extra & PNX_SHORTHAND)) { | if (right && right->pn_arity == PN_LIST && (right->pn_xflags & PNX_DESTRUCT)) { |
3665 | js_ReportCompileErrorNumber(cx, TS(tc->parseContext), right, | js_ReportCompileErrorNumber(cx, TS(tc->compiler), right, |
3666 | JSREPORT_ERROR, JSMSG_BAD_OBJECT_INIT); | JSREPORT_ERROR, JSMSG_BAD_OBJECT_INIT); |
3667 | return JS_FALSE; | return JS_FALSE; |
3668 | } | } |
# | Line 1962 | Line 3670 |
3670 | ||
3671 | fpvd.table.ops = NULL; | fpvd.table.ops = NULL; |
3672 | lhs = left->pn_head; | lhs = left->pn_head; |
if (lhs && lhs->pn_type == TOK_DEFSHARP) { | ||
pn = lhs; | ||
goto no_var_name; | ||
} | ||
3673 | if (left->pn_type == TOK_RB) { | if (left->pn_type == TOK_RB) { |
3674 | rhs = (right && right->pn_type == left->pn_type) | rhs = (right && right->pn_type == left->pn_type) |
3675 | ? right->pn_head | ? right->pn_head |
# | Line 2067 | Line 3770 |
3770 | if (data && | if (data && |
3771 | data->binder == BindLet && | data->binder == BindLet && |
3772 | OBJ_BLOCK_COUNT(cx, tc->blockChain) == 0) { | OBJ_BLOCK_COUNT(cx, tc->blockChain) == 0) { |
3773 | ok = js_DefineNativeProperty(cx, tc->blockChain, | ok = !!js_DefineNativeProperty(cx, tc->blockChain, |
3774 | ATOM_TO_JSID(cx->runtime-> | ATOM_TO_JSID(cx->runtime-> |
3775 | atomState.emptyAtom), | atomState.emptyAtom), |
3776 | JSVAL_VOID, NULL, NULL, | JSVAL_VOID, NULL, NULL, |
3777 | JSPROP_ENUMERATE | | JSPROP_ENUMERATE | |
3778 | JSPROP_PERMANENT | | JSPROP_PERMANENT | |
3779 | JSPROP_SHARED, | JSPROP_SHARED, |
3780 | SPROP_HAS_SHORTID, 0, NULL); | SPROP_HAS_SHORTID, 0, NULL); |
3781 | if (!ok) | if (!ok) |
3782 | goto out; | goto out; |
3783 | } | } |
# | Line 2087 | Line 3790 |
3790 | return ok; | return ok; |
3791 | ||
3792 | no_var_name: | no_var_name: |
3793 | js_ReportCompileErrorNumber(cx, TS(tc->parseContext), pn, JSREPORT_ERROR, | js_ReportCompileErrorNumber(cx, TS(tc->compiler), pn, JSREPORT_ERROR, |
3794 | JSMSG_NO_VARIABLE_NAME); | JSMSG_NO_VARIABLE_NAME); |
3795 | ok = JS_FALSE; | ok = JS_FALSE; |
3796 | goto out; | goto out; |
3797 | } | } |
3798 | ||
3799 | /* | |
3800 | * This is a greatly pared down version of CheckDestructuring that extends the | |
3801 | * pn_pos.end source coordinate of each name in a destructuring binding such as | |
3802 | * | |
3803 | * var [x, y] = [function () y, 42]; | |
3804 | * | |
3805 | * to cover its corresponding initializer, so that the initialized binding does | |
3806 | * not appear to dominate any closures in its initializer. See bug 496134. | |
3807 | * | |
3808 | * The quick-and-dirty dominance computation in JSCompiler::setFunctionKinds is | |
3809 | * not very precise. With one-pass SSA construction from structured source code | |
3810 | * (see "Single-Pass Generation of Static Single Assignment Form for Structured | |
3811 | * Languages", Brandis and Mössenböck), we could do much better. | |
3812 | * | |
3813 | * See CheckDestructuring, immediately above. If you change either of these | |
3814 | * functions, you might have to change the other to match. | |
3815 | */ | |
3816 | static JSBool | |
3817 | UndominateInitializers(JSParseNode *left, JSParseNode *right, JSTreeContext *tc) | |
3818 | { | |
3819 | FindPropValData fpvd; | |
3820 | JSParseNode *lhs, *rhs; | |
3821 | ||
3822 | JS_ASSERT(left->pn_type != TOK_ARRAYCOMP); | |
3823 | JS_ASSERT(right); | |
3824 | ||
3825 | #if JS_HAS_DESTRUCTURING_SHORTHAND | |
3826 | if (right->pn_arity == PN_LIST && (right->pn_xflags & PNX_DESTRUCT)) { | |
3827 | js_ReportCompileErrorNumber(tc->compiler->context, TS(tc->compiler), right, | |
3828 | JSREPORT_ERROR, JSMSG_BAD_OBJECT_INIT); | |
3829 | return JS_FALSE; | |
3830 | } | |
3831 | #endif | |
3832 | ||
3833 | if (right->pn_type != left->pn_type) | |
3834 | return JS_TRUE; | |
3835 | ||
3836 | fpvd.table.ops = NULL; | |
3837 | lhs = left->pn_head; | |
3838 | if (left->pn_type == TOK_RB) { | |
3839 | rhs = right->pn_head; | |
3840 | ||
3841 | while (lhs && rhs) { | |
3842 | /* Nullary comma is an elision; binary comma is an expression.*/ | |
3843 | if (lhs->pn_type != TOK_COMMA || lhs->pn_arity != PN_NULLARY) { | |
3844 | if (lhs->pn_type == TOK_RB || lhs->pn_type == TOK_RC) { | |
3845 | if (!UndominateInitializers(lhs, rhs, tc)) | |
3846 | return JS_FALSE; | |
3847 | } else { | |
3848 | lhs->pn_pos.end = rhs->pn_pos.end; | |
3849 | } | |
3850 | } | |
3851 | ||
3852 | lhs = lhs->pn_next; | |
3853 | rhs = rhs->pn_next; | |
3854 | } | |
3855 | } else { | |
3856 | JS_ASSERT(left->pn_type == TOK_RC); | |
3857 | fpvd.numvars = left->pn_count; | |
3858 | fpvd.maxstep = 0; | |
3859 | ||
3860 | while (lhs) { | |
3861 | JS_ASSERT(lhs->pn_type == TOK_COLON); | |
3862 | JSParseNode *pn = lhs->pn_right; | |
3863 | ||
3864 | rhs = FindPropertyValue(right, lhs->pn_left, &fpvd); | |
3865 | if (pn->pn_type == TOK_RB || pn->pn_type == TOK_RC) { | |
3866 | if (rhs && !UndominateInitializers(pn, rhs, tc)) | |
3867 | return JS_FALSE; | |
3868 | } else { | |
3869 | if (rhs) | |
3870 | pn->pn_pos.end = rhs->pn_pos.end; | |
3871 | } | |
3872 | ||
3873 | lhs = lhs->pn_next; | |
3874 | } | |
3875 | } | |
3876 | return JS_TRUE; | |
3877 | } | |
3878 | ||
3879 | static JSParseNode * | static JSParseNode * |
3880 | DestructuringExpr(JSContext *cx, BindData *data, JSTreeContext *tc, | DestructuringExpr(JSContext *cx, BindData *data, JSTreeContext *tc, |
3881 | JSTokenType tt) | JSTokenType tt) |
3882 | { | { |
3883 | JSTokenStream *ts; | |
3884 | JSParseNode *pn; | JSParseNode *pn; |
3885 | ||
3886 | pn = PrimaryExpr(cx, TS(tc->parseContext), tc, tt, JS_FALSE); | ts = TS(tc->compiler); |
3887 | ts->flags |= TSF_DESTRUCTURING; | |
3888 | pn = PrimaryExpr(cx, ts, tc, tt, JS_FALSE); | |
3889 | ts->flags &= ~TSF_DESTRUCTURING; | |
3890 | if (!pn) | if (!pn) |
3891 | return NULL; | return NULL; |
3892 | if (!CheckDestructuring(cx, data, pn, NULL, tc)) | if (!CheckDestructuring(cx, data, pn, NULL, tc)) |
# | Line 2107 | Line 3894 |
3894 | return pn; | return pn; |
3895 | } | } |
3896 | ||
3897 | // Currently used only #if JS_HAS_DESTRUCTURING, in Statement's TOK_FOR case. | /* |
3898 | * Currently used only #if JS_HAS_DESTRUCTURING, in Statement's TOK_FOR case. | |
3899 | * This function assumes the cloned tree is for use in the same statement and | |
3900 | * binding context as the original tree. | |
3901 | */ | |
3902 | static JSParseNode * | static JSParseNode * |
3903 | CloneParseTree(JSContext *cx, JSParseNode *opn, JSTreeContext *tc) | CloneParseTree(JSParseNode *opn, JSTreeContext *tc) |
3904 | { | { |
3905 | JSParseNode *pn, *pn2, *opn2; |