diff --git a/js/src/jsinterp.c b/js/src/jsinterp.c index 2b4e5519babf..3a7c1a8e1dd0 100644 --- a/js/src/jsinterp.c +++ b/js/src/jsinterp.c @@ -843,91 +843,116 @@ js_ComputeThis(JSContext *cx, JSBool lazy, jsval *argv) #if JS_HAS_NO_SUCH_METHOD -static JSBool -NoSuchMethod(JSContext *cx, uintN argc, jsval *vp, uint32 flags) +JSClass js_NoSuchMethodClass = { + "NoSuchMethod", + JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_IS_ANONYMOUS | + JSCLASS_HAS_CACHED_PROTO(JSProto_NoSuchMethod), + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + +JSObject* +js_InitNoSuchMethodClass(JSContext *cx, JSObject* obj) { - JSStackFrame *fp; - JSObject *thisp, *argsobj; - JSAtom *atom; - jsval roots[3]; + JSObject *proto; + + proto = JS_InitClass(cx, obj, NULL, &js_NoSuchMethodClass, NULL, 0, NULL, + NULL, NULL, NULL); + if (!proto) + return NULL; + + OBJ_SET_PROTO(cx, proto, NULL); + return proto; +} + +/* + * When JSOP_CALLPROP or JSOP_CALLELEM does not find the method property of + * the base object, we search for the __noSuchMethod__ method in the base. + * If it exists, we store the method and the property's id into an object of + * NoSuchMethod class and store this object into the callee's stack slot. + * Later, js_Invoke will recognise such an object and transfer control to + * NoSuchMethod that invokes the method like: + * + * this.__noSuchMethod__(id, args) + * + * where id is the name of the method that this invocation attempted to + * call by name, and args is an Array containing this invocation's actual + * parameters. + */ +JSBool +js_OnUnknownMethod(JSContext *cx, jsval *vp) +{ + JSObject *obj; JSTempValueRooter tvr; jsid id; JSBool ok; - jsbytecode *pc; - /* - * NB: vp[1] aka |this| may be null still, requiring - * js_ComputeGlobalThis. - */ - JS_ASSERT(JSVAL_IS_PRIMITIVE(vp[0])); - JS_ASSERT(JSVAL_IS_OBJECT(vp[1])); - fp = cx->fp; - - /* From here on, control must flow through label out: to return. */ - memset(roots, 0, sizeof roots); - JS_PUSH_TEMP_ROOT(cx, JS_ARRAY_LENGTH(roots), roots, &tvr); - - thisp = JSVAL_TO_OBJECT(vp[1]); - if (!thisp) { - thisp = js_ComputeGlobalThis(cx, JS_FALSE, vp + 2); - if (!thisp) { - ok = JS_FALSE; - goto out; - } - fp->thisp = thisp; - } + JS_ASSERT(!JSVAL_IS_PRIMITIVE(vp[1])); + obj = JSVAL_TO_OBJECT(vp[1]); + JS_PUSH_SINGLE_TEMP_ROOT(cx, JSVAL_NULL, &tvr); + /* From here on, control must flow through label out:. */ id = ATOM_TO_JSID(cx->runtime->atomState.noSuchMethodAtom); #if JS_HAS_XML_SUPPORT - if (OBJECT_IS_XML(cx, thisp)) { + if (OBJECT_IS_XML(cx, obj)) { JSXMLObjectOps *ops; - ops = (JSXMLObjectOps *) thisp->map->ops; - thisp = ops->getMethod(cx, thisp, id, &roots[2]); - if (!thisp) { + ops = (JSXMLObjectOps *) obj->map->ops; + obj = ops->getMethod(cx, obj, id, &tvr.u.value); + if (!obj) { ok = JS_FALSE; goto out; } - vp[1] = OBJECT_TO_JSVAL(thisp); + vp[1] = OBJECT_TO_JSVAL(obj); } else #endif { - ok = OBJ_GET_PROPERTY(cx, thisp, id, &roots[2]); + ok = OBJ_GET_PROPERTY(cx, obj, id, &tvr.u.value); if (!ok) goto out; } - if (JSVAL_IS_PRIMITIVE(roots[2])) - goto not_function; - - pc = (jsbytecode *) vp[-(intN)fp->script->depth]; - switch ((JSOp) *pc) { - case JSOP_NAME: - case JSOP_GETPROP: - case JSOP_CALLPROP: - GET_ATOM_FROM_BYTECODE(fp->script, pc, 0, atom); - roots[0] = ATOM_KEY(atom); - argsobj = js_NewArrayObject(cx, argc, vp + 2); - if (!argsobj) { + if (JSVAL_IS_PRIMITIVE(tvr.u.value)) { + vp[0] = tvr.u.value; + } else { + obj = js_NewObject(cx, &js_NoSuchMethodClass, NULL, NULL); + if (!obj) { ok = JS_FALSE; goto out; } - roots[1] = OBJECT_TO_JSVAL(argsobj); - ok = js_InternalInvoke(cx, thisp, roots[2], flags | JSINVOKE_INTERNAL, - 2, roots, &vp[0]); - break; - - default: - goto not_function; + STOBJ_SET_SLOT(obj, JSSLOT_PRIVATE, tvr.u.value); + STOBJ_SET_SLOT(obj, JSSLOT_PRIVATE + 1, vp[0]); + vp[0] = OBJECT_TO_JSVAL(obj); } + ok = JS_TRUE; out: JS_POP_TEMP_ROOT(cx, &tvr); return ok; +} - not_function: - js_ReportIsNotFunction(cx, vp, flags & JSINVOKE_FUNFLAGS); - ok = JS_FALSE; - goto out; +static JSBool +NoSuchMethod(JSContext *cx, uintN argc, jsval *vp, uint32 flags) +{ + JSObject *obj, *thisp, *argsobj; + jsval fval, idval, args[2]; + + JS_ASSERT(!JSVAL_IS_PRIMITIVE(vp[0])); + obj = JSVAL_TO_OBJECT(vp[0]); + JS_ASSERT(STOBJ_GET_CLASS(obj) == &js_NoSuchMethodClass); + fval = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE); + idval = OBJ_GET_SLOT(cx, obj, JSSLOT_PRIVATE + 1); + + JS_ASSERT(!JSVAL_IS_PRIMITIVE(vp[1])); + thisp = JSVAL_TO_OBJECT(vp[1]); + + args[0] = idval; + argsobj = js_NewArrayObject(cx, argc, vp + 2); + if (!argsobj) + return JS_FALSE; + args[1] = OBJECT_TO_JSVAL(argsobj); + return js_InternalInvoke(cx, thisp, fval, flags | JSINVOKE_INTERNAL, + 2, args, &vp[0]); } #endif /* JS_HAS_NO_SUCH_METHOD */ @@ -987,31 +1012,20 @@ js_Invoke(JSContext *cx, uintN argc, jsval *vp, uintN flags) mark = JS_ARENA_MARK(&cx->stackPool); v = *vp; - /* - * A callee must be an object reference, unless its 'this' parameter - * implements the __noSuchMethod__ method, in which case that method will - * be called like so: - * - * this.__noSuchMethod__(id, args) - * - * where id is the name of the method that this invocation attempted to - * call by name, and args is an Array containing this invocation's actual - * parameters. - */ - if (JSVAL_IS_PRIMITIVE(v)) { -#if JS_HAS_NO_SUCH_METHOD - if (cx->fp && cx->fp->script && !(flags & JSINVOKE_INTERNAL)) { - ok = NoSuchMethod(cx, argc, vp, flags); - goto out2; - } -#endif + if (JSVAL_IS_PRIMITIVE(v)) goto bad; - } funobj = JSVAL_TO_OBJECT(v); parent = OBJ_GET_PARENT(cx, funobj); clasp = OBJ_GET_CLASS(cx, funobj); if (clasp != &js_FunctionClass) { +#if JS_HAS_NO_SUCH_METHOD + if (clasp == &js_NoSuchMethodClass) { + ok = NoSuchMethod(cx, argc, vp, flags); + goto out2; + } +#endif + /* Function is inlined, all other classes use object ops. */ ops = funobj->map->ops; @@ -2153,8 +2167,6 @@ js_DumpOpMeters() * to a function that may invoke the interpreter. RESTORE_SP must be called * only after return from js_Invoke, because only js_Invoke changes fp->sp. */ -#define PUSH(v) (*sp++ = (v)) -#define POP() (*--sp) #define SAVE_SP(fp) \ (JS_ASSERT((fp)->script || !(fp)->spbase || (sp) == (fp)->spbase), \ (fp)->sp = sp) @@ -2171,22 +2183,17 @@ js_DumpOpMeters() #define RESTORE_SP_AND_PC(fp) (RESTORE_SP(fp), pc = (fp)->pc) #define ASSERT_SAVED_SP_AND_PC(fp) JS_ASSERT((fp)->sp == sp && (fp)->pc == pc); -/* - * Push the generating bytecode's pc onto the parallel pc stack that runs - * depth slots below the operands. - * - * NB: PUSH_OPND uses sp, depth, and pc from its lexical environment. See - * js_Interpret for these local variables' declarations and uses. - */ -#define PUSH_OPND(v) (sp[-depth] = (jsval)pc, PUSH(v)) -#define STORE_OPND(n,v) (sp[(n)-depth] = (jsval)pc, sp[n] = (v)) +#define PUSH(v) (*sp++ = (v)) +#define PUSH_OPND(v) PUSH(v) +#define STORE_OPND(n,v) (sp[n] = (v)) +#define POP() (*--sp) #define POP_OPND() POP() #define FETCH_OPND(n) (sp[n]) /* - * Push the jsdouble d using sp, depth, and pc from the lexical environment. - * Try to convert d to a jsint that fits in a jsval, otherwise GC-alloc space - * for it and push a reference. + * Push the jsdouble d using sp from the lexical environment. Try to convert d + * to a jsint that fits in a jsval, otherwise GC-alloc space for it and push a + * reference. */ #define STORE_NUMBER(cx, n, d) \ JS_BEGIN_MACRO \ @@ -2416,7 +2423,7 @@ js_Interpret(JSContext *cx, jsbytecode *pc, jsval *result) JSVersion currentVersion, originalVersion; JSBool ok, cond; JSTrapHandler interruptHandler; - jsint depth, len; + jsint len; jsval *sp; void *mark; jsbytecode *endpc, *pc2; @@ -2589,28 +2596,26 @@ js_Interpret(JSContext *cx, jsbytecode *pc, jsval *result) /* From this point the control must flow through the label exit. * - * Allocate operand and pc stack slots for the script's worst-case depth, - * unless we're resuming a generator. + * Allocate operand stack slots for the script's worst-case depth, unless + * we're resuming a generator. */ - depth = (jsint) script->depth; if (JS_LIKELY(!fp->spbase)) { ASSERT_NOT_THROWING(cx); JS_ASSERT(!fp->sp); - sp = js_AllocRawStack(cx, (uintN)(2 * depth), &mark); + sp = js_AllocRawStack(cx, script->depth, &mark); if (!sp) { mark = NULL; ok = JS_FALSE; goto exit; } JS_ASSERT(mark); - sp += depth; /* skip pc stack slots */ fp->spbase = sp; SAVE_SP(fp); } else { JS_ASSERT(fp->flags & JSFRAME_GENERATOR); mark = NULL; RESTORE_SP(fp); - JS_ASSERT(JS_UPTRDIFF(sp, fp->spbase) <= depth * sizeof(jsval)); + JS_ASSERT((size_t) (sp - fp->spbase) <= script->depth); JS_ASSERT(JS_PROPERTY_CACHE(cx).disabled >= 0); JS_PROPERTY_CACHE(cx).disabled += js_CountWithBlocks(cx, fp); @@ -2765,10 +2770,6 @@ interrupt: END_CASE(JSOP_POPN) BEGIN_CASE(JSOP_SWAP) - vp = sp - depth; /* swap generating pc's for the decompiler */ - ltmp = vp[-1]; - vp[-1] = vp[-2]; - sp[-2] = ltmp; rtmp = sp[-1]; sp[-1] = sp[-2]; sp[-2] = rtmp; @@ -2894,13 +2895,9 @@ interrupt: /* Restore the calling script's interpreter registers. */ script = fp->script; - depth = (jsint) script->depth; atoms = script->atomMap.vector; pc = fp->pc; - /* Store the generating pc for the return value. */ - vp[-depth] = (jsval)pc; - /* Resume execution in the calling frame. */ inlineCallCount--; if (JS_LIKELY(ok)) { @@ -3135,7 +3132,7 @@ interrupt: case JSOP_FORLOCAL: slot = GET_UINT16(pc); - JS_ASSERT(slot < (uintN)depth); + JS_ASSERT(slot < script->depth); vp = &fp->spbase[slot]; GC_POKE(cx, *vp); *vp = rval; @@ -3191,20 +3188,14 @@ interrupt: BEGIN_CASE(JSOP_DUP) JS_ASSERT(sp > fp->spbase); - vp = sp - 1; /* address top of stack */ - rval = *vp; - vp -= depth; /* address generating pc */ - vp[1] = *vp; + rval = FETCH_OPND(-1); PUSH(rval); END_CASE(JSOP_DUP) BEGIN_CASE(JSOP_DUP2) JS_ASSERT(sp - 2 >= fp->spbase); - vp = sp - 1; /* address top of stack */ - lval = vp[-1]; - rval = *vp; - vp -= depth; /* address generating pc */ - vp[1] = vp[2] = *vp; + lval = FETCH_OPND(-2); + rval = FETCH_OPND(-1); PUSH(lval); PUSH(rval); END_CASE(JSOP_DUP2) @@ -3548,7 +3539,6 @@ interrupt: END_CASE(JSOP_STRICTNE) BEGIN_CASE(JSOP_CASE) - pc2 = (jsbytecode *) sp[-2-depth]; STRICT_EQUALITY_OP(==); (void) POP(); if (cond) { @@ -3556,12 +3546,10 @@ interrupt: CHECK_BRANCH(len); DO_NEXT_OP(len); } - sp[-depth] = (jsval)pc2; PUSH(lval); END_CASE(JSOP_CASE) BEGIN_CASE(JSOP_CASEX) - pc2 = (jsbytecode *) sp[-2-depth]; STRICT_EQUALITY_OP(==); (void) POP(); if (cond) { @@ -3569,7 +3557,6 @@ interrupt: CHECK_BRANCH(len); DO_NEXT_OP(len); } - sp[-depth] = (jsval)pc2; PUSH(lval); END_CASE(JSOP_CASEX) @@ -3779,7 +3766,6 @@ interrupt: goto error; sp[-1] = rval; } - sp[-1-depth] = (jsval)pc; END_CASE(JSOP_POS) BEGIN_CASE(JSOP_NEW) @@ -3792,7 +3778,6 @@ interrupt: if (!js_InvokeConstructor(cx, vp, argc)) goto error; sp = vp + 1; - vp[-depth] = (jsval)pc; LOAD_INTERRUPT_HANDLER(cx); END_CASE(JSOP_NEW) @@ -3837,8 +3822,7 @@ interrupt: END_CASE(JSOP_TYPEOF) BEGIN_CASE(JSOP_VOID) - (void) POP_OPND(); - PUSH_OPND(JSVAL_VOID); + STORE_OPND(-1, JSVAL_VOID); END_CASE(JSOP_VOID) BEGIN_CASE(JSOP_INCELEM) @@ -4110,7 +4094,7 @@ interrupt: BEGIN_CASE(JSOP_GETLOCALPROP) i = UINT16_LEN; slot = GET_UINT16(pc); - JS_ASSERT(slot < (uintN)depth); + JS_ASSERT(slot < script->depth); PUSH_OPND(fp->spbase[slot]); len = JSOP_GETLOCALPROP_LENGTH; goto do_getprop_body; @@ -4243,7 +4227,7 @@ interrupt: JS_UNLOCK_OBJ(cx, obj2); STORE_OPND(-1, rval); PUSH_OPND(lval); - goto end_callname; + goto end_callprop; } } else { entry = NULL; @@ -4284,7 +4268,7 @@ interrupt: STORE_OPND(-2, rval); } - end_callname: + end_callprop: /* Wrap primitive lval in object clothing if necessary. */ if (JSVAL_IS_PRIMITIVE(lval)) { /* FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=412571 */ @@ -4296,6 +4280,15 @@ interrupt: goto error; } } +#if JS_HAS_NO_SUCH_METHOD + if (JS_UNLIKELY(rval == JSVAL_VOID)) { + LOAD_ATOM(0); + sp[-2] = ATOM_KEY(atom); + SAVE_SP_AND_PC(fp); + if (!js_OnUnknownMethod(cx, sp - 2)) + goto error; + } +#endif } END_CASE(JSOP_CALLPROP) @@ -4522,10 +4515,10 @@ interrupt: if (JSVAL_IS_INT(rval)) { if (OBJ_IS_DENSE_ARRAY(cx, obj)) { jsuint length; - + length = ARRAY_DENSE_LENGTH(obj); i = JSVAL_TO_INT(rval); - if ((jsuint)i < length && + if ((jsuint)i < length && i < obj->fslots[JSSLOT_ARRAY_LENGTH]) { rval = obj->dslots[i]; if (rval != JSVAL_HOLE) @@ -4551,8 +4544,19 @@ interrupt: * CALLPROP does. See bug 362910. */ ELEMENT_OP(-1, OBJ_GET_PROPERTY(cx, obj, id, &rval)); - STORE_OPND(-2, rval); - STORE_OPND(-1, OBJECT_TO_JSVAL(obj)); +#if JS_HAS_NO_SUCH_METHOD + if (JS_UNLIKELY(rval == JSVAL_VOID)) { + sp[-2] = sp[-1]; + sp[-1] = OBJECT_TO_JSVAL(obj); + SAVE_SP_AND_PC(fp); + if (!js_OnUnknownMethod(cx, sp - 2)) + goto error; + } else +#endif + { + STORE_OPND(-2, rval); + STORE_OPND(-1, OBJECT_TO_JSVAL(obj)); + } END_CASE(JSOP_CALLELEM) BEGIN_CASE(JSOP_SETELEM) @@ -4618,9 +4622,8 @@ interrupt: sizeof(jsval)); nvars = fun->u.i.nvars; script = fun->u.i.script; - depth = (jsint) script->depth; atoms = script->atomMap.vector; - nslots = nframeslots + nvars + 2 * depth; + nslots = nframeslots + nvars + script->depth; /* Allocate missing expected args adjacent to actuals. */ missing = (fun->nargs > argc) ? fun->nargs - argc : 0; @@ -4710,7 +4713,6 @@ interrupt: sp = newsp; while (nvars--) PUSH(JSVAL_VOID); - sp += depth; newifp->frame.spbase = sp; SAVE_SP(&newifp->frame); @@ -4763,7 +4765,6 @@ interrupt: RESTORE_SP(fp); JS_ASSERT(fp->pc == pc); script = fp->script; - depth = (jsint) script->depth; atoms = script->atomMap.vector; js_FreeRawStack(cx, newmark); goto error; @@ -4791,15 +4792,10 @@ interrupt: * this frame's operand stack, take the slow path. */ nargs = fun->u.n.minargs - argc; - if (sp + nargs > fp->spbase + depth) + if (sp + nargs > fp->spbase + script->depth) goto do_invoke; do { - /* - * Use PUSH_OPND to set the proper pc values for - * the extra arguments. The decompiler relies on - * this. - */ - PUSH_OPND(JSVAL_VOID); + PUSH(JSVAL_VOID); } while (--nargs != 0); SAVE_SP(fp); } @@ -4819,7 +4815,6 @@ interrupt: if (!ok) goto error; sp = vp + 1; - vp[-depth] = (jsval)pc; goto end_call; } } @@ -4836,7 +4831,6 @@ interrupt: } #endif sp = vp + 1; - vp[-depth] = (jsval)pc; LOAD_INTERRUPT_HANDLER(cx); if (!ok) goto error; @@ -4876,7 +4870,6 @@ interrupt: vp = sp - argc - 2; ok = js_Invoke(cx, argc, vp, 0); sp = vp + 1; - vp[-depth] = (jsval)pc; LOAD_INTERRUPT_HANDLER(cx); if (!ok) goto error; @@ -6218,7 +6211,7 @@ interrupt: */ JS_ASSERT(sp - fp->spbase >= 2); slot = GET_UINT16(pc); - JS_ASSERT(slot + 1 < (uintN)depth); + JS_ASSERT(slot + 1 < script->depth); fp->spbase[slot] = POP_OPND(); END_CASE(JSOP_SETLOCALPOP) @@ -6528,7 +6521,7 @@ interrupt: LOAD_OBJECT(0); JS_ASSERT(fp->spbase + OBJ_BLOCK_DEPTH(cx, obj) == sp); vp = sp + OBJ_BLOCK_COUNT(cx, obj); - JS_ASSERT(vp <= fp->spbase + depth); + JS_ASSERT(vp <= fp->spbase + script->depth); while (sp < vp) { STORE_OPND(0, JSVAL_VOID); sp++; @@ -6565,7 +6558,7 @@ interrupt: ? fp->blockChain : fp->scopeChain); - JS_ASSERT(fp->spbase <= blocksp && blocksp <= fp->spbase + depth); + JS_ASSERT((size_t) (blocksp - fp->spbase) <= script->depth); #endif if (fp->blockChain) { JS_ASSERT(OBJ_GET_CLASS(cx, fp->blockChain) == &js_BlockClass); @@ -6599,7 +6592,7 @@ interrupt: BEGIN_CASE(JSOP_GETLOCAL) BEGIN_CASE(JSOP_CALLLOCAL) slot = GET_UINT16(pc); - JS_ASSERT(slot < (uintN)depth); + JS_ASSERT(slot < script->depth); PUSH_OPND(fp->spbase[slot]); if (op == JSOP_CALLLOCAL) PUSH_OPND(JSVAL_NULL); @@ -6607,7 +6600,7 @@ interrupt: BEGIN_CASE(JSOP_SETLOCAL) slot = GET_UINT16(pc); - JS_ASSERT(slot < (uintN)depth); + JS_ASSERT(slot < script->depth); vp = &fp->spbase[slot]; GC_POKE(cx, *vp); *vp = FETCH_OPND(-1); @@ -6616,7 +6609,7 @@ interrupt: /* NB: This macro doesn't use JS_BEGIN_MACRO/JS_END_MACRO around its body. */ #define FAST_LOCAL_INCREMENT_OP(PRE,OPEQ,MINMAX) \ slot = GET_UINT16(pc); \ - JS_ASSERT(slot < (uintN)depth); \ + JS_ASSERT(slot < script->depth); \ vp = fp->spbase + slot; \ rval = *vp; \ if (!JSVAL_IS_INT(rval) || rval == INT_TO_JSVAL(JSVAL_INT_##MINMAX)) \ @@ -6690,7 +6683,7 @@ interrupt: BEGIN_CASE(JSOP_ARRAYPUSH) slot = GET_UINT16(pc); - JS_ASSERT(slot < (uintN)depth); + JS_ASSERT(slot < script->depth); lval = fp->spbase[slot]; obj = JSVAL_TO_OBJECT(lval); JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_ArrayClass); diff --git a/js/src/jsinterp.h b/js/src/jsinterp.h index ac6b553bc154..b4bcc4c8d906 100644 --- a/js/src/jsinterp.h +++ b/js/src/jsinterp.h @@ -60,6 +60,9 @@ JS_BEGIN_EXTERN_C * with well-known slots, if possible. */ struct JSStackFrame { + jsval *sp; /* stack pointer */ + jsbytecode *pc; /* program counter */ + jsval *spbase; /* operand stack base */ JSObject *callobj; /* lazily created Call object */ JSObject *argsobj; /* lazily created arguments object */ JSObject *varobj; /* variables object, where vars go */ @@ -75,9 +78,6 @@ struct JSStackFrame { JSStackFrame *down; /* previous frame */ void *annotation; /* used by Java security */ JSObject *scopeChain; /* scope chain */ - jsbytecode *pc; /* program counter */ - jsval *sp; /* stack pointer */ - jsval *spbase; /* operand stack base */ uintN sharpDepth; /* array/object initializer depth */ JSObject *sharpArray; /* scope for #n= initializer vars */ uint32 flags; /* frame flags -- see below */ @@ -492,6 +492,9 @@ js_InternNonIntElementId(JSContext *cx, JSObject *obj, jsval idval, jsid *idp); extern JSBool js_ImportProperty(JSContext *cx, JSObject *obj, jsid id); +extern JSBool +js_OnUnknownMethod(JSContext *cx, jsval *vp); + /* * JS_OPMETER helper functions. */ diff --git a/js/src/jsopcode.c b/js/src/jsopcode.c index 52ad701f8abc..f2c8399dc9a2 100644 --- a/js/src/jsopcode.c +++ b/js/src/jsopcode.c @@ -4763,21 +4763,34 @@ js_DecompileFunction(JSPrinter *jp) return JS_TRUE; } -#undef LOCAL_ASSERT_RV +/* + * Find the depth of the operand stack when the interpreter reaches the given + * pc in script. When pcstack is not null, it must be an array with space for + * at least script->depth elements. On return the array will contain pointers + * to opcodes that populated the interpreter's current operand stack. + * + * This function cannot raise an exception or error. However, due to a risk of + * potential bugs when modeling the stack, the function returns -1 if it + * detects an inconsistency in the model. Such an inconsistency triggers an + * assert in a debug build. + */ +static intN +ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *pc, + jsbytecode **pcstack); char * js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v, JSString *fallback) { - JSStackFrame *fp, *down; + JSStackFrame *fp; jsbytecode *pc, *begin, *end; - jsval *sp, *spbase, *base, *limit; - intN depth, pcdepth; JSScript *script; JSOp op; const JSCodeSpec *cs; jssrcnote *sn; - ptrdiff_t len, oplen; + ptrdiff_t len; + intN pcdepth; + jsval *sp; JSPrinter *jp; char *name; @@ -4790,113 +4803,73 @@ js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v, if (!fp) goto do_fallback; - /* Try to find sp's generating pc depth slots under it on the stack. */ pc = fp->pc; - sp = fp->sp; - spbase = fp->spbase; - if ((uintN)(sp - spbase) > fp->script->depth) { - /* - * Preparing to make an internal invocation, using an argv stack - * segment pushed just above fp's operand stack space. Such an argv - * stack has no generating pc "basement", so we must fall back. - */ + if (!pc) + goto do_fallback; + script = fp->script; + if (pc < script->main || script->code + script->length <= pc) { + JS_NOT_REACHED("bug"); goto do_fallback; } - if (spindex == JSDVG_SEARCH_STACK) { - if (!pc) { - /* - * Current frame is native: look under it for a scripted call - * in which a decompilable bytecode string that generated the - * value as an actual argument might exist. - */ - JS_ASSERT(!fp->script && !(fp->fun && FUN_INTERPRETED(fp->fun))); - down = fp->down; - if (!down) - goto do_fallback; - script = down->script; - spbase = down->spbase; - base = fp->argv; - limit = base + fp->argc; + if (spindex != JSDVG_IGNORE_STACK) { + jsbytecode **pcstack; + + /* + * Prepare computing pcstack containing pointers to opcodes that + * populated interpreter's stack with its current content. + */ + pcstack = (jsbytecode **) + JS_malloc(cx, script->depth * sizeof *pcstack); + if (!pcstack) + return NULL; + pcdepth = ReconstructPCStack(cx, script, fp->pc, pcstack); + if (pcdepth < 0) + goto release_pcstack; + + if (spindex != JSDVG_SEARCH_STACK) { + JS_ASSERT(spindex < 0); + pcdepth += spindex; + if (pcdepth < 0) + goto release_pcstack; + pc = pcstack[pcdepth]; } else { /* - * This should be a script activation, either a top-level - * script or a scripted function. But be paranoid about calls - * to js_DecompileValueGenerator from code that hasn't fully - * initialized a (default-all-zeroes) frame. + * We search from fp->sp to base to find the most recently + * calculated value matching v under assumption that it is + * it that caused exception, see bug 328664. */ - script = fp->script; - spbase = base = fp->spbase; - limit = fp->sp; - } + JS_ASSERT((size_t) (fp->sp - fp->spbase) <= fp->script->depth); + sp = fp->sp; + do { + if (sp == fp->spbase) { + pcdepth = -1; + goto release_pcstack; + } + } while (*--sp != v); - /* - * Pure paranoia about default-zeroed frames being active while - * js_DecompileValueGenerator is called. It can't hurt much now; - * error reporting performance is not an issue. - */ - if (!script || !base || !limit) - goto do_fallback; - - /* - * Try to find operand-generating pc depth slots below sp. - * - * In the native case, we know the arguments have generating pc's - * under them, on account of fp->down->script being non-null: all - * compiled scripts get depth slots for generating pc's allocated - * upon activation, at the top of js_Interpret. - * - * In the script or scripted function case, the same reasoning - * applies to fp rather than to fp->down. - * - * We search from limit to base to find the most recently calculated - * value matching v under assumption that it is it that caused - * exception, see bug 328664. - */ - for (sp = limit;;) { - if (sp <= base) - goto do_fallback; - --sp; - if (*sp == v) { - depth = (intN)script->depth; - sp -= depth; - pc = (jsbytecode *) *sp; - break; + if (sp >= fp->spbase + pcdepth) { + /* + * This happens when the value comes from a temporary slot + * that the interpreter uses for GC roots. Assume that it is + * fp->pc that caused the exception. + */ + pc = fp->pc; + } else { + pc = pcstack[sp - fp->spbase]; } } - } else { - /* - * At this point, pc may or may not be null, i.e., we could be in - * a script activation, or we could be in a native frame that was - * called by another native function. Check pc and script. - */ - if (!pc) - goto do_fallback; - script = fp->script; - if (!script) - goto do_fallback; - if (spindex != JSDVG_IGNORE_STACK) { - JS_ASSERT(spindex < 0); - depth = (intN)script->depth; -#if !JS_HAS_NO_SUCH_METHOD - JS_ASSERT(-depth <= spindex); -#endif - sp = fp->sp + spindex; - if ((jsuword) (sp - fp->spbase) < (jsuword) depth) - pc = (jsbytecode *) *(sp - depth); - } + release_pcstack: + JS_free(cx, pcstack); + if (pcdepth < 0) + goto do_fallback; } /* - * Again, be paranoid, this time about possibly loading an invalid pc - * from fp->sp[spindex - script->depth)]. + * We know the address of the opcode that triggered the diagnostic. Find + * the decompilation limits for the opcode and its stack depth. */ - if (JS_UPTRDIFF(pc, script->code) >= (jsuword)script->length) { - pc = fp->pc; - if (!pc) - goto do_fallback; - } op = (JSOp) *pc; if (op == JSOP_TRAP) op = JS_GetTrapOpcode(cx, script, pc); @@ -4952,16 +4925,57 @@ js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v, if (len <= 0) goto do_fallback; + pcdepth = ReconstructPCStack(cx, script, begin, NULL); + if (pcdepth < 0) + goto do_fallback; + + name = NULL; + jp = JS_NEW_PRINTER(cx, "js_DecompileValueGenerator", fp->fun, 0, JS_FALSE); + if (jp) { + jp->dvgfence = end; + if (js_DecompileCode(jp, script, begin, (uintN)len, (uintN)pcdepth)) { + name = (jp->sprinter.base) ? jp->sprinter.base : (char *) ""; + name = JS_strdup(cx, name); + } + js_DestroyPrinter(jp); + } + return name; + + do_fallback: + if (!fallback) { + fallback = js_ValueToSource(cx, v); + if (!fallback) + return NULL; + } + return js_DeflateString(cx, JSSTRING_CHARS(fallback), + JSSTRING_LENGTH(fallback)); +} + +static intN +ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *pc, + jsbytecode **pcstack) +{ + intN pcdepth, nuses, ndefs; + jsbytecode *begin; + JSOp op; + const JSCodeSpec *cs; + ptrdiff_t oplen; + jssrcnote *sn; + uint32 type; + +#define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, -1); + /* - * Walk forward from script->main and compute starting stack depth. + * Walk forward from script->main and compute the stack depth and stack of + * operand-generating opcode PCs in pcstack. + * * FIXME: Code to compute oplen copied from js_Disassemble1 and reduced. * FIXME: Optimize to use last empty-stack sequence point. */ + LOCAL_ASSERT(script->main <= pc && pc < script->code + script->length); pcdepth = 0; + begin = pc; for (pc = script->main; pc < begin; pc += oplen) { - uint32 type; - intN nuses, ndefs; - op = (JSOp) *pc; if (op == JSOP_TRAP) op = JS_GetTrapOpcode(cx, script, pc); @@ -4970,6 +4984,7 @@ js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v, if (op == JSOP_POPN) { pcdepth -= GET_UINT16(pc); + LOCAL_ASSERT(pcdepth >= 0); continue; } @@ -5002,6 +5017,7 @@ js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v, * since we have moved beyond the IFEQ now. */ --pcdepth; + LOCAL_ASSERT(pcdepth >= 0); } } @@ -5068,7 +5084,7 @@ js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v, nuses = GET_UINT16(pc); } pcdepth -= nuses; - JS_ASSERT(pcdepth >= 0); + LOCAL_ASSERT(pcdepth >= 0); ndefs = cs->ndefs; if (op == JSOP_FINALLY) { @@ -5083,27 +5099,65 @@ js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v, JS_ASSERT(OBJ_BLOCK_DEPTH(cx, obj) == pcdepth); ndefs = OBJ_BLOCK_COUNT(cx, obj); } + + LOCAL_ASSERT(pcdepth + ndefs <= script->depth); + if (pcstack) { + intN i; + jsbytecode *pc2; + + /* + * Fill the slots that the opcode defines withs its pc unless it + * just reshuffle the stack. In the latter case we want to + * preserve the opcode that generated the original value. + */ + switch (op) { + default: + for (i = 0; i != ndefs; ++i) + pcstack[pcdepth + i] = pc; + break; + + case JSOP_CASE: + case JSOP_CASEX: + /* Keep the switch value. */ + JS_ASSERT(ndefs == 1); + break; + + case JSOP_DUP: + JS_ASSERT(ndefs == 2); + pcstack[pcdepth + 1] = pcstack[pcdepth]; + break; + + case JSOP_DUP2: + JS_ASSERT(ndefs == 4); + pcstack[pcdepth + 2] = pcstack[pcdepth]; + pcstack[pcdepth + 3] = pcstack[pcdepth + 1]; + break; + + case JSOP_SWAP: + JS_ASSERT(ndefs == 2); + pc2 = pcstack[pcdepth]; + pcstack[pcdepth] = pcstack[pcdepth + 1]; + pcstack[pcdepth + 1] = pc2; + break; + + case JSOP_LEAVEBLOCKEXPR: + /* + * The decompiler wants [leaveblockexpr], not [enterblock], to + * be left on pcstack after a simulated let expression. + */ + JS_ASSERT(ndefs == 0); + LOCAL_ASSERT(pcdepth >= 1); + LOCAL_ASSERT(*pcstack[pcdepth - 1] == JSOP_ENTERBLOCK); + pcstack[pcdepth - 1] = pc; + break; + } + } pcdepth += ndefs; } + LOCAL_ASSERT(pc == begin); + return pcdepth; - name = NULL; - jp = JS_NEW_PRINTER(cx, "js_DecompileValueGenerator", fp->fun, 0, JS_FALSE); - if (jp) { - jp->dvgfence = end; - if (js_DecompileCode(jp, script, begin, (uintN)len, (uintN)pcdepth)) { - name = (jp->sprinter.base) ? jp->sprinter.base : (char *) ""; - name = JS_strdup(cx, name); - } - js_DestroyPrinter(jp); - } - return name; - - do_fallback: - if (!fallback) { - fallback = js_ValueToSource(cx, v); - if (!fallback) - return NULL; - } - return js_DeflateString(cx, JSSTRING_CHARS(fallback), - JSSTRING_LENGTH(fallback)); +#undef LOCAL_ASSERT } + +#undef LOCAL_ASSERT_RV diff --git a/js/src/jsopcode.tbl b/js/src/jsopcode.tbl index 582980ea8919..c568c793d64c 100644 --- a/js/src/jsopcode.tbl +++ b/js/src/jsopcode.tbl @@ -272,7 +272,7 @@ OPDEF(JSOP_UNUSED117, 117,"unused117", NULL, 1, 0, 0, 0, JOF_BYTE) * lval if false; and DEFAULT is POP lval and GOTO. */ OPDEF(JSOP_CONDSWITCH,118,"condswitch", NULL, 1, 0, 0, 0, JOF_BYTE|JOF_PARENHEAD) -OPDEF(JSOP_CASE, 119,"case", NULL, 3, 1, 0, 0, JOF_JUMP) +OPDEF(JSOP_CASE, 119,"case", NULL, 3, 2, 1, 0, JOF_JUMP) OPDEF(JSOP_DEFAULT, 120,"default", NULL, 3, 1, 0, 0, JOF_JUMP) /* @@ -355,7 +355,7 @@ OPDEF(JSOP_IFNEX, 141,"ifnex", NULL, 5, 1, 0, 0, JOF_JUMPX| OPDEF(JSOP_ORX, 142,"orx", NULL, 5, 1, 0, 5, JOF_JUMPX|JOF_DETECTING) OPDEF(JSOP_ANDX, 143,"andx", NULL, 5, 1, 0, 6, JOF_JUMPX|JOF_DETECTING) OPDEF(JSOP_GOSUBX, 144,"gosubx", NULL, 5, 0, 0, 0, JOF_JUMPX) -OPDEF(JSOP_CASEX, 145,"casex", NULL, 5, 1, 0, 0, JOF_JUMPX) +OPDEF(JSOP_CASEX, 145,"casex", NULL, 5, 2, 1, 0, JOF_JUMPX) OPDEF(JSOP_DEFAULTX, 146,"defaultx", NULL, 5, 1, 0, 0, JOF_JUMPX) OPDEF(JSOP_TABLESWITCHX, 147,"tableswitchx",NULL, -1, 1, 0, 0, JOF_TABLESWITCHX|JOF_DETECTING|JOF_PARENHEAD) OPDEF(JSOP_LOOKUPSWITCHX, 148,"lookupswitchx",NULL, -1, 1, 0, 0, JOF_LOOKUPSWITCHX|JOF_DETECTING|JOF_PARENHEAD) diff --git a/js/src/jsproto.tbl b/js/src/jsproto.tbl index a26f9488f52b..47dabc78adbd 100644 --- a/js/src/jsproto.tbl +++ b/js/src/jsproto.tbl @@ -66,6 +66,12 @@ # define GENERATOR_INIT js_InitNullClass #endif +#if JS_HAS_NO_SUCH_METHOD +# define NO_SUCH_METHOD_INIT js_InitNoSuchMethodClass +#else +# define NO_SUCH_METHOD_INIT js_InitNullClass +#endif + #if JS_HAS_FILE_OBJECT # define FILE_INIT js_InitFileClass #else @@ -109,6 +115,7 @@ JS_PROTO(UnusedProto28, 28, js_InitNullClass) JS_PROTO(File, 29, FILE_INIT) JS_PROTO(Block, 30, js_InitBlockClass) JS_PROTO(XMLFilter, 31, XMLFILTER_INIT) +JS_PROTO(NoSuchMethod, 32, NO_SUCH_METHOD_INIT) #undef SCRIPT_INIT #undef XML_INIT @@ -118,3 +125,4 @@ JS_PROTO(XMLFilter, 31, XMLFILTER_INIT) #undef ATTRIBUTE_INIT #undef GENERATOR_INIT #undef FILE_INIT +#undef NO_SUCH_METHOD_INIT