From 32ade36e5903bef6cf57e92d6509c5158ba126d0 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Thu, 2 Aug 2012 09:20:08 -0700 Subject: [PATCH] Bug 767274: New expression decompiler. r=luke --- .../tests/basic/expression-autopsy.js | 96 +++ js/src/jit-test/tests/basic/testBug566556.js | 2 +- js/src/jit-test/tests/basic/testBug579647.js | 2 +- .../tests/basic/testInitSingletons.js | 2 +- js/src/jsopcode.cpp | 573 +++++++++++++++--- js/src/tests/e4x/Regress/regress-355478.js | 2 +- js/src/tests/ecma_3/Object/8.6.1-01.js | 2 +- js/src/tests/js1_5/Regress/regress-372364.js | 4 +- js/src/tests/js1_6/Array/regress-304828.js | 12 +- js/src/tests/js1_6/Regress/regress-350417.js | 2 +- js/src/tests/js1_7/block/regress-352616.js | 6 +- .../js1_7/extensions/regress-355052-01.js | 2 +- .../js1_7/extensions/regress-355052-02.js | 2 +- .../js1_7/extensions/regress-355052-03.js | 2 +- .../tests/js1_7/regress/regress-352870-02.js | 2 +- .../tests/js1_8/extensions/regress-469625.js | 2 +- .../tests/js1_8_1/regress/regress-420399.js | 4 +- .../tests/js1_8_5/regress/regress-469758.js | 2 +- 18 files changed, 599 insertions(+), 120 deletions(-) create mode 100644 js/src/jit-test/tests/basic/expression-autopsy.js diff --git a/js/src/jit-test/tests/basic/expression-autopsy.js b/js/src/jit-test/tests/basic/expression-autopsy.js new file mode 100644 index 00000000000..e3ec683c1d7 --- /dev/null +++ b/js/src/jit-test/tests/basic/expression-autopsy.js @@ -0,0 +1,96 @@ +function check_one(expected, f, err) { + var failed = true; + try { + f(); + failed = false; + } catch (ex) { + var s = ex.toString(); + assertEq(s.slice(0, 11), "TypeError: "); + assertEq(s.slice(-err.length), err); + assertEq(s.slice(11, -err.length), expected); + } + if (!failed) + throw new Error("didn't fail"); +} +ieval = eval; +function check(expr, expected=expr) { + var end, err; + for ([end, err] of [[".random_prop", " is undefined"], ["()", " is not a function"]]) { + var statement = "o = {};" + expr + end, f; + var cases = [ + // Global scope + function () { + ieval("var o, undef;\n" + statement); + }, + // Function scope + Function("o", "undef", statement), + // Function scope with variables + Function("var o, undef;\n" + statement), + // Function scope with some different arugments + Function("arg1", "arg2", "var o, undef;\n" + statement), + // Deoptimized function scope + Function("o", "undef", "with (Object) {}\n" + statement), + // Inside with + Function("with (Object) { " + statement + " }"), + // Closure + Function("o", "undef", "function myfunc() { return o + undef; }\n" + statement), + // Let definitions in a block + Function("{ let o, undef;\n" + statement + "}"), + // Let block + Function("let (o, undef) { " + statement + " }"), + // Let block with some other variables + Function("var v1, v2; let (o, undef) { " + statement + " }"), + // Shadowed let block + Function("o", "undef", "let (o, undef) { " + statement + " }"), + // Let in a switch + Function("var x = 4; switch (x) { case 4: let o, undef;" + statement + "\ncase 6: break;}"), + // Let in for-in + Function("var undef, o; for (let z in [1, 2]) { " + statement + " }"), + // The more lets the merrier + Function("let (x=4, y=5) { x + y; }\nlet (a, b, c) { a + b - c; }\nlet (o, undef) {" + statement + " }"), + // Let destructuring + Function("o", "undef", "let ([] = 4) {} let (o, undef) { " + statement + " }"), + // Try-catch blocks + Function("o", "undef", "try { let q = 4; try { let p = 4; } catch (e) {} } catch (e) {} let (o, undef) { " + statement + " }") + ]; + for (var f of cases) { + check_one(expected, f, err); + } + } +} + +check("undef"); +check("o.b"); +check("o.length"); +check("o[true]"); +check("o[false]"); +check("o[null]"); +check("o[0]"); +check("o[1]"); +check("o[3]"); +check("o[256]"); +check("o[65536]"); +check("o[268435455]"); +check("o['1.1']"); +check("o[4 + 'h']", "o['4h']"); +check("this.x"); +check("ieval(undef)", "ieval(...)"); +check("ieval.call()", "ieval.call(...)"); + +for (let tok of ["|", "^", "&", "==", "!==", "===", "!==", "<", "<=", ">", ">=", + ">>", "<<", ">>>", "+", "-", "*", "/", "%"]) { + check("o[(undef " + tok + " 4)]"); +} + +check("o[!(o)]"); +check("o[~(o)]"); +check("o[+ (o)]"); +check("o[- (o)]"); + +// A few one off tests +check_one("6", (function () { 6() }), " is not a function"); +check_one("Array.prototype.reverse.call(...)", (function () { Array.prototype.reverse.call('123'); }), " is read-only"); +check_one("null", function () { var [{ x }] = [null, {}]; }, " has no properties"); + +// Check fallback behavior +check_one("undefined", (function () { for (let x of undefined) {} }), " has no properties"); diff --git a/js/src/jit-test/tests/basic/testBug566556.js b/js/src/jit-test/tests/basic/testBug566556.js index 71f0ba6cc1d..244be57d228 100644 --- a/js/src/jit-test/tests/basic/testBug566556.js +++ b/js/src/jit-test/tests/basic/testBug566556.js @@ -6,4 +6,4 @@ try { } catch (e) { msg = e.toString(); } -assertEq(msg, "TypeError: (void 0) is not an object or null"); +assertEq(msg, "TypeError: undefined is not an object or null"); diff --git a/js/src/jit-test/tests/basic/testBug579647.js b/js/src/jit-test/tests/basic/testBug579647.js index 92f76c6ef06..027f643a96f 100644 --- a/js/src/jit-test/tests/basic/testBug579647.js +++ b/js/src/jit-test/tests/basic/testBug579647.js @@ -5,7 +5,7 @@ try { a = "" for each(x in [0, 0, 0, 0]) { a %= x - } ( - a)() + } ( let (a=-a) a)() } catch (e) { actual = '' + e; } diff --git a/js/src/jit-test/tests/basic/testInitSingletons.js b/js/src/jit-test/tests/basic/testInitSingletons.js index a949fe132a6..fb8dd641c27 100644 --- a/js/src/jit-test/tests/basic/testInitSingletons.js +++ b/js/src/jit-test/tests/basic/testInitSingletons.js @@ -28,7 +28,7 @@ assertEq(q[4294967295], 2); try { [1,2,3,{a:0,b:1}].foo.bar; -} catch (e) { assertEq(e.message, "[1, 2, 3, {a:0, b:1}].foo is undefined"); } +} catch (e) { assertEq(e.message.search("\.foo is undefined") != -1, true); } var a = [1 + 1, 3 * 2, 6 - 5, 14 % 6, 15 / 5, 1 << 3, 8 >> 2, 5 | 2, 5 ^ 3, ~3, -3,"a" + "b", !true, !false]; diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 19a564f59e9..b30e0e09f0b 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -1331,7 +1331,7 @@ CopyDecompiledTextForDecomposedOp(JSPrinter *jp, jsbytecode *pc) */ static int ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *pc, - jsbytecode **pcstack, jsbytecode **lastDecomposedPC); + jsbytecode **pcstack); #define FAILED_EXPRESSION_DECOMPILER ((char *) 1) @@ -5734,105 +5734,493 @@ js_DecompileFunction(JSPrinter *jp) return JS_TRUE; } -char * -js_DecompileValueGenerator(JSContext *cx, int spindex, jsval v, - JSString *fallback) +static JSObject * +GetBlockChainAtPC(JSContext *cx, JSScript *script, jsbytecode *pc) { - StackFrame *fp; - JSScript *script; - jsbytecode *pc; + jsbytecode *start = script->main(); + + JS_ASSERT(pc >= start && pc < script->code + script->length); + JSObject *blockChain = NULL; + for (jsbytecode *p = start; p < pc; p += GetBytecodeLength(p)) { + JSOp op = JSOp(*p); + + switch (op) { + case JSOP_ENTERBLOCK: + case JSOP_ENTERLET0: + case JSOP_ENTERLET1: { + JSObject *child = script->getObject(p); + JS_ASSERT_IF(blockChain, child->asBlock().stackDepth() >= blockChain->asBlock().stackDepth()); + blockChain = child; + break; + } + case JSOP_LEAVEBLOCK: + case JSOP_LEAVEBLOCKEXPR: + case JSOP_LEAVEFORLETIN: { + // Some LEAVEBLOCK instructions are due to early exits via + // return/break/etc. from block-scoped loops and functions. We + // should ignore these instructions, since they don't really signal + // the end of the block. + jssrcnote *sn = js_GetSrcNote(script, p); + if (!(sn && SN_TYPE(sn) == SRC_HIDDEN)) { + JS_ASSERT(blockChain); + blockChain = blockChain->asStaticBlock().enclosingBlock(); + JS_ASSERT_IF(blockChain, blockChain->isBlock()); + } + break; + } + default: + break; + } + } + + return blockChain; +} + +class PCStack +{ + JSContext *cx; + jsbytecode **stack; + int depth_; + + public: + explicit PCStack(JSContext *cx) + : cx(cx), + stack(NULL), + depth_(0) + {} + ~PCStack(); + bool init(JSContext *cx, JSScript *script, jsbytecode *pc); + int depth() const { return depth_; } + jsbytecode *operator[](int i) const; +}; + +PCStack::~PCStack() +{ + cx->free_(stack); +} + +bool +PCStack::init(JSContext *cx, JSScript *script, jsbytecode *pc) +{ + stack = static_cast(cx->malloc_(StackDepth(script) * sizeof(*stack))); + if (!stack) + return false; + depth_ = ReconstructPCStack(cx, script, pc, stack); + JS_ASSERT(depth_ >= 0); + return true; +} + +/* Indexes the pcstack. */ +jsbytecode * +PCStack::operator[](int i) const +{ + if (i < 0) { + i += depth_; + JS_ASSERT(i >= 0); + } + JS_ASSERT(i < depth_); + return stack[i]; +} + +/* + * The expression decompiler is invoked by error handling code to produce a + * string representation of the erroring expression. As it's only a debugging + * tool, it only supports basic expressions. For anything complicated, it simply + * puts "(intermediate value)" into the error result. + * + * Here's the basic algorithm: + * + * 1. Find the stack location of the value whose expression we wish to + * decompile. The error handler can explicitly pass this as an + * argument. Otherwise, we search backwards down the stack for the offending + * value. + * + * 2. Call ReconstructPCStack with the current frame's pc. This creates a stack + * of pcs parallel to the interpreter stack; given an interpreter stack + * location, the corresponding pc stack location contains the opcode that pushed + * the value in the interpreter. Now, with the result of step 1, we have the + * opcode responsible for pushing the value we want to decompile. + * + * 3. Pass the opcode to decompilePC. decompilePC is the main decompiler + * routine, responsible for a string representation of the expression that + * generated a certain stack location. decompilePC looks at one opcode and + * returns the JS source equivalent of that opcode. + * + * 4. Expressions can, of course, contain subexpressions. For example, the + * literals "4" and "5" are subexpressions of the addition operator in "4 + + * 5". If we need to decompile a subexpression, we call decompilePC (step 2) + * recursively on the operands' pcs. The result is a depth-first traversal of + * the expression tree. + * + */ +struct ExpressionDecompiler +{ + JSContext *cx; + StackFrame *fp; + RootedScript script; + RootedFunction fun; + BindingVector *localNames; + Sprinter sprinter; + + ExpressionDecompiler(JSContext *cx, JSScript *script, JSFunction *fun) + : cx(cx), + script(cx, script), + fun(cx, fun), + localNames(NULL), + sprinter(cx) + {} + ~ExpressionDecompiler(); + bool init(); + bool decompilePC(jsbytecode *pc); + JSAtom *getVar(unsigned slot); + JSAtom *getArg(unsigned slot); + JSAtom *findLetVar(jsbytecode *pc, unsigned depth); + JSAtom *loadAtom(jsbytecode *pc); + bool quote(JSString *s, uint32_t quote); + bool write(const char *s); + bool write(JSString *s); + bool getOutput(char **out); +}; + +bool +ExpressionDecompiler::decompilePC(jsbytecode *pc) +{ + JS_ASSERT(script->code <= pc && pc < script->code + script->length); + + PCStack pcstack(cx); + if (!pcstack.init(cx, script, pc)) + return NULL; + + JSOp op = (JSOp)*pc; + + // None of these stack-writing ops generates novel values. + JS_ASSERT(op != JSOP_CASE && op != JSOP_DUP && op != JSOP_DUP2); + + if (const char *token = CodeToken[op]) { + // Handle simple cases of binary and unary operators. + switch (js_CodeSpec[op].nuses) { + case 2: { + jssrcnote *sn = js_GetSrcNote(script, pc); + if (!sn || SN_TYPE(sn) != SRC_ASSIGNOP) + return write("(") && + decompilePC(pcstack[-2]) && + write(" ") && + write(token) && + write(" ") && + decompilePC(pcstack[-1]) && + write(")"); + break; + } + case 1: + return write(token) && + write("(") && + decompilePC(pcstack[-1]) && + write(")"); + default: + break; + } + } + + switch (op) { + case JSOP_GETGNAME: + case JSOP_CALLGNAME: + case JSOP_NAME: + case JSOP_CALLNAME: + return write(loadAtom(pc)); + case JSOP_GETARG: + case JSOP_CALLARG: { + unsigned slot = GET_ARGNO(pc); + JSAtom *atom = getArg(slot); + if (!atom) + break; // Destructuring + return write(atom); + } + case JSOP_GETLOCAL: + case JSOP_CALLLOCAL: { + unsigned i = GET_SLOTNO(pc); + JSAtom *atom; + if (i >= script->nfixed) { + i -= script->nfixed; + JS_ASSERT(i < unsigned(pcstack.depth())); + atom = findLetVar(pc, i); + if (!atom) + return decompilePC(pcstack[i]); // Destructing temporary + } else { + atom = getVar(i); + } + JS_ASSERT(atom); + return write(atom); + } + case JSOP_CALLALIASEDVAR: + case JSOP_GETALIASEDVAR: { + JSAtom *atom = ScopeCoordinateName(cx->runtime, script, pc); + JS_ASSERT(atom); + return write(atom); + } + case JSOP_LENGTH: + case JSOP_GETPROP: + case JSOP_CALLPROP: { + JSAtom *prop = (op == JSOP_LENGTH) ? cx->runtime->atomState.lengthAtom : loadAtom(pc); + if (!decompilePC(pcstack[-1])) + return false; + if (IsIdentifier(prop)) + return write(".") && + quote(prop, '\0'); + else + return write("[") && + quote(prop, '\'') && + write("]") >= 0; + return true; + } + case JSOP_GETELEM: + case JSOP_CALLELEM: + return decompilePC(pcstack[-2]) && + write("[") && + decompilePC(pcstack[-1]) && + write("]"); + case JSOP_NULL: + return write(js_null_str); + case JSOP_TRUE: + return write(js_true_str); + case JSOP_FALSE: + return write(js_false_str); + case JSOP_ZERO: + case JSOP_ONE: + case JSOP_INT8: + case JSOP_UINT16: + case JSOP_UINT24: + case JSOP_INT32: { + int32_t i; + switch (op) { + case JSOP_ZERO: + i = 0; + break; + case JSOP_ONE: + i = 1; + break; + case JSOP_INT8: + i = GET_INT8(pc); + break; + case JSOP_UINT16: + i = GET_UINT16(pc); + break; + case JSOP_UINT24: + i = GET_UINT24(pc); + break; + case JSOP_INT32: + i = GET_INT32(pc); + break; + default: + JS_NOT_REACHED("wat?"); + } + return sprinter.printf("%d", i) >= 0; + } + case JSOP_STRING: + return quote(loadAtom(pc), '"'); + case JSOP_UNDEFINED: + return write(js_undefined_str); + case JSOP_THIS: + // |this| could convert to a very long object initialiser, so cite it by + // its keyword name. + return write(js_this_str); + case JSOP_CALL: + case JSOP_FUNCALL: + return decompilePC(pcstack[-(GET_ARGC(pc) + 2)]) && + write("(...)"); + default: + break; + } + return write("(intermediate value)"); +} + +ExpressionDecompiler::~ExpressionDecompiler() +{ + cx->delete_(localNames); +} + +bool +ExpressionDecompiler::init() +{ + if (!sprinter.init()) + return false; + + localNames = cx->new_(cx); + if (!localNames) + return false; + if (!GetOrderedBindings(cx, script->bindings, localNames)) + return false; + + return true; +} + +bool +ExpressionDecompiler::write(const char *s) +{ + return sprinter.put(s) >= 0; +} + +bool +ExpressionDecompiler::write(JSString *s) +{ + return sprinter.putString(s) >= 0; +} + +bool +ExpressionDecompiler::quote(JSString *s, uint32_t quote) +{ + return QuoteString(&sprinter, s, quote) >= 0; +} + +JSAtom * +ExpressionDecompiler::loadAtom(jsbytecode *pc) +{ + return script->getAtom(GET_UINT32_INDEX(pc)); +} + +JSAtom * +ExpressionDecompiler::findLetVar(jsbytecode *pc, unsigned depth) +{ + if (script->hasObjects()) { + JSObject *chain = GetBlockChainAtPC(cx, script, pc); + if (!chain) + return NULL; + JS_ASSERT(chain->isBlock()); + do { + BlockObject &block = chain->asBlock(); + uint32_t blockDepth = block.stackDepth(); + uint32_t blockCount = block.slotCount(); + if (uint32_t(depth - blockDepth) < uint32_t(blockCount)) { + for (Shape::Range r(block.lastProperty()); !r.empty(); r.popFront()) { + const Shape &shape = r.front(); + if (shape.shortid() == int(depth - blockDepth)) { + // !JSID_IS_ATOM(shape.propid()) happens for empty + // destructuring variables in lets. They can be safely + // ignored. + if (JSID_IS_ATOM(shape.propid())) + return JSID_TO_ATOM(shape.propid()); + } + } + } + chain = chain->enclosingScope(); + } while (chain->isBlock()); + } + return NULL; +} + +JSAtom * +ExpressionDecompiler::getArg(unsigned slot) +{ + JS_ASSERT(fun); + JS_ASSERT(slot < script->bindings.count()); + return (*localNames)[slot].maybeName; +} + +JSAtom * +ExpressionDecompiler::getVar(unsigned slot) +{ + JS_ASSERT(fun); + slot += fun->nargs; + JS_ASSERT(slot < script->bindings.count()); + return (*localNames)[slot].maybeName; +} + +bool +ExpressionDecompiler::getOutput(char **res) +{ + ptrdiff_t len = sprinter.stringEnd() - sprinter.stringAt(0); + *res = static_cast(cx->malloc_(len + 1)); + if (!*res) + return false; + js_memcpy(*res, sprinter.stringAt(0), len); + (*res)[len] = 0; + return true; +} + +static bool +FindStartPC(JSContext *cx, JSScript *script, int spindex, Value v, jsbytecode **valuepc) +{ + jsbytecode *current = *valuepc; + + if (spindex == JSDVG_IGNORE_STACK) + return true; + + *valuepc = NULL; + + PCStack pcstack(cx); + if (!pcstack.init(cx, script, current)) + return false; + + if (spindex == JSDVG_SEARCH_STACK) { + // 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. + Value *stackBase = cx->regs().spForStackDepth(0); + Value *sp = cx->regs().sp; + do { + if (sp == stackBase) + return true; + } while (*--sp != v); + if (sp < stackBase + pcstack.depth()) + *valuepc = pcstack[sp - stackBase]; + } else { + *valuepc = pcstack[spindex]; + } + return true; +} + +static bool +DecompileExpressionFromStack(JSContext *cx, int spindex, Value v, char **res) +{ JS_ASSERT(spindex < 0 || spindex == JSDVG_IGNORE_STACK || spindex == JSDVG_SEARCH_STACK); + *res = NULL; + if (!cx->hasfp() || !cx->fp()->isScriptFrame()) - goto do_fallback; + return true; + StackFrame *fp = js_GetTopStackFrame(cx, FRAME_EXPAND_ALL); + JSScript *script = fp->script(); + jsbytecode *valuepc = cx->regs().pc; + JSFunction *fun = fp->maybeFun(); + JS_ASSERT(script->code <= valuepc && valuepc < script->code + script->length); - fp = js_GetTopStackFrame(cx, FRAME_EXPAND_ALL); - script = fp->script(); - pc = cx->regs().pc; - JS_ASSERT(script->code <= pc && pc < script->code + script->length); + // Give up if in prologue. + if (valuepc < script->main()) + return true; - if (pc < script->main()) - goto do_fallback; + if (!FindStartPC(cx, script, spindex, v, &valuepc)) + return false; + if (!valuepc) + return true; - if (spindex != JSDVG_IGNORE_STACK) { - jsbytecode **pcstack; + ExpressionDecompiler ea(cx, script, fun); + if (!ea.init()) + return false; + if (!ea.decompilePC(valuepc)) + return false; - /* - * Prepare computing pcstack containing pointers to opcodes that - * populated interpreter's stack with its current content. - */ - pcstack = (jsbytecode **) - cx->malloc_(StackDepth(script) * sizeof *pcstack); - if (!pcstack) - return NULL; - jsbytecode *lastDecomposedPC = NULL; - int pcdepth = ReconstructPCStack(cx, script, pc, pcstack, &lastDecomposedPC); - 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 { - /* - * 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. - */ - Value *stackBase = cx->regs().spForStackDepth(0); - Value *sp = cx->regs().sp; - do { - if (sp == stackBase) { - pcdepth = -1; - goto release_pcstack; - } - } while (*--sp != v); - - /* - * The value may have come from beyond stackBase + pcdepth, meaning - * that it came from a temporary slot pushed by the interpreter or - * arguments pushed for an Invoke call. Only update pc if beneath - * stackBase + pcdepth. If above, the value may or may not be - * produced by the current pc. Since it takes a fairly contrived - * combination of calls to produce a situation where this is not - * what we want, we just use the current pc. - * - * If we are in the middle of a decomposed opcode, use the outer - * 'fat' opcode itself. Any source notes for the operation which - * are needed during decompilation will be associated with the - * outer opcode. - */ - if (sp < stackBase + pcdepth) { - pc = pcstack[sp - stackBase]; - if (lastDecomposedPC) { - size_t len = GetDecomposeLength(lastDecomposedPC, - js_CodeSpec[*lastDecomposedPC].length); - if (unsigned(pc - lastDecomposedPC) < len) - pc = lastDecomposedPC; - } - } - } - - release_pcstack: - cx->free_(pcstack); - if (pcdepth < 0) - goto do_fallback; - } + return ea.getOutput(res); +} +char * +js_DecompileValueGenerator(JSContext *cx, int spindex, jsval v, + JSString *fallback) +{ { - char *name = DecompileExpression(cx, script, fp->maybeFun(), pc); - if (name != FAILED_EXPRESSION_DECOMPILER) - return name; + char *result; + if (!DecompileExpressionFromStack(cx, spindex, v, &result)) + return NULL; + if (result) { + if (strcmp(result, "(intermediate value)")) + return result; + cx->free_(result); + } } - - do_fallback: if (!fallback) { + if (v.isUndefined()) + return JS_strdup(cx, js_undefined_str); // Prevent users from seeing "(void 0)" fallback = js_ValueToSource(cx, v); if (!fallback) return NULL; @@ -5927,7 +6315,7 @@ DecompileExpression(JSContext *cx, JSScript *script, JSFunction *fun, if (!g.pcstack) return NULL; - int pcdepth = ReconstructPCStack(cx, script, begin, g.pcstack, NULL); + int pcdepth = ReconstructPCStack(cx, script, begin, g.pcstack); if (pcdepth < 0) return FAILED_EXPRESSION_DECOMPILER; @@ -5946,7 +6334,7 @@ DecompileExpression(JSContext *cx, JSScript *script, JSFunction *fun, unsigned js_ReconstructStackDepth(JSContext *cx, JSScript *script, jsbytecode *pc) { - return ReconstructPCStack(cx, script, pc, NULL, NULL); + return ReconstructPCStack(cx, script, pc, NULL); } #define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, -1); @@ -6008,7 +6396,7 @@ SimulateOp(JSContext *cx, JSScript *script, JSOp op, const JSCodeSpec *cs, static int ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *target, - jsbytecode **pcstack, jsbytecode **lastDecomposedPC) + jsbytecode **pcstack) { /* * Walk forward from script->main and compute the stack depth and stack of @@ -6025,15 +6413,10 @@ ReconstructPCStack(JSContext *cx, JSScript *script, jsbytecode *target, for (; pc < target; pc += oplen) { JSOp op = JSOp(*pc); const JSCodeSpec *cs = &js_CodeSpec[op]; - oplen = cs->length; - if (oplen < 0) - oplen = js_GetVariableBytecodeLength(pc); + oplen = GetBytecodeLength(pc); - if (cs->format & JOF_DECOMPOSE) { - if (lastDecomposedPC) - *lastDecomposedPC = pc; + if (cs->format & JOF_DECOMPOSE) continue; - } /* * A (C ? T : E) expression requires skipping either T (if target is in diff --git a/js/src/tests/e4x/Regress/regress-355478.js b/js/src/tests/e4x/Regress/regress-355478.js index e3e32b3dc2f..f5f888ba67d 100644 --- a/js/src/tests/e4x/Regress/regress-355478.js +++ b/js/src/tests/e4x/Regress/regress-355478.js @@ -14,7 +14,7 @@ var expect = ''; printBugNumber(BUGNUMBER); START(summary); -expect = 'TypeError: .hasOwnProperty is not a constructor'; +expect = 'TypeError: (intermediate value).hasOwnProperty is not a constructor'; actual = ''; try diff --git a/js/src/tests/ecma_3/Object/8.6.1-01.js b/js/src/tests/ecma_3/Object/8.6.1-01.js index c39437c0da7..7a176765e51 100644 --- a/js/src/tests/ecma_3/Object/8.6.1-01.js +++ b/js/src/tests/ecma_3/Object/8.6.1-01.js @@ -15,7 +15,7 @@ enterFunc (String (BUGNUMBER)); // should throw an error in strict mode var actual = ''; -var expect = 's.length is read-only'; +var expect = '"length" is read-only'; var status = summary + ': Throw if STRICT and WERROR is enabled'; if (!options().match(/strict/)) diff --git a/js/src/tests/js1_5/Regress/regress-372364.js b/js/src/tests/js1_5/Regress/regress-372364.js index 4a93e539547..1421186e46c 100644 --- a/js/src/tests/js1_5/Regress/regress-372364.js +++ b/js/src/tests/js1_5/Regress/regress-372364.js @@ -21,7 +21,7 @@ function test() printStatus (summary); print('See Also bug 365891'); - expect = /TypeError: a\(1\) (has no properties|is null)/; + expect = /TypeError: a\(.+\) (has no properties|is null)/; try { function a(){return null;} a(1)[0]; @@ -32,7 +32,7 @@ function test() } reportMatch(expect, actual, summary); - expect = /TypeError: \/a\/\.exec\("b"\) (has no properties|is null)/; + expect = /TypeError: \(intermediate value\).exec\(.+\) (has no properties|is null)/; try { /a/.exec("b")[0]; diff --git a/js/src/tests/js1_6/Array/regress-304828.js b/js/src/tests/js1_6/Array/regress-304828.js index d04c01bc684..9baa2aac549 100644 --- a/js/src/tests/js1_6/Array/regress-304828.js +++ b/js/src/tests/js1_6/Array/regress-304828.js @@ -29,7 +29,7 @@ reportCompare(expect, actual, summary + ': join'); // reverse value = '123'; -expect = 'TypeError: Array.prototype.reverse.call(value) is read-only'; +expect = 'TypeError: Array.prototype.reverse.call(...) is read-only'; try { actual = Array.prototype.reverse.call(value) + ''; @@ -42,7 +42,7 @@ reportCompare(expect, actual, summary + ': reverse'); // sort value = 'cba'; -expect = 'TypeError: Array.prototype.sort.call(value) is read-only'; +expect = 'TypeError: Array.prototype.sort.call(...) is read-only'; try { actual = Array.prototype.sort.call(value) + ''; @@ -69,7 +69,7 @@ reportCompare('abc', value, summary + ': push'); // pop value = 'abc'; -expect = "TypeError: property Array.prototype.pop.call(value) is non-configurable and can't be deleted"; +expect = "TypeError: property Array.prototype.pop.call(...) is non-configurable and can't be deleted"; try { actual = Array.prototype.pop.call(value); @@ -83,7 +83,7 @@ reportCompare('abc', value, summary + ': pop'); // unshift value = 'def'; -expect = 'TypeError: Array.prototype.unshift.call(value, "a", "b", "c") is read-only'; +expect = 'TypeError: Array.prototype.unshift.call(...) is read-only'; try { actual = Array.prototype.unshift.call(value, 'a', 'b', 'c'); @@ -97,7 +97,7 @@ reportCompare('def', value, summary + ': unshift'); // shift value = 'abc'; -expect = 'TypeError: Array.prototype.shift.call(value) is read-only'; +expect = 'TypeError: Array.prototype.shift.call(...) is read-only'; try { actual = Array.prototype.shift.call(value); @@ -111,7 +111,7 @@ reportCompare('abc', value, summary + ': shift'); // splice value = 'abc'; -expect = 'TypeError: Array.prototype.splice.call(value, 1, 1) is read-only'; +expect = 'TypeError: Array.prototype.splice.call(...) is read-only'; try { actual = Array.prototype.splice.call(value, 1, 1) + ''; diff --git a/js/src/tests/js1_6/Regress/regress-350417.js b/js/src/tests/js1_6/Regress/regress-350417.js index b56a5f3c4d3..303a52e21a6 100644 --- a/js/src/tests/js1_6/Regress/regress-350417.js +++ b/js/src/tests/js1_6/Regress/regress-350417.js @@ -20,7 +20,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - expect = 'TypeError: y.a = [2 for each (p in [])] is not a function'; + expect = 'TypeError: [] is not a function'; try { eval('y = {}; (y.a = [2 for each (p in [])])();'); diff --git a/js/src/tests/js1_7/block/regress-352616.js b/js/src/tests/js1_7/block/regress-352616.js index 9655b5e61e8..f00f26710c8 100644 --- a/js/src/tests/js1_7/block/regress-352616.js +++ b/js/src/tests/js1_7/block/regress-352616.js @@ -19,7 +19,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - expect = /TypeError: (\(let \(b = 1\) 2\).c is not a function|Cannot find function c.)/; + expect = /TypeError: (.+\.c is not a function|Cannot find function c.)/; actual = 'No Error'; try { @@ -32,7 +32,7 @@ function test() reportMatch(expect, actual, summary + ': 1'); - expect = /TypeError: (\(let \(b = 1, d = 2\) 2\).c is not a function|Cannot find function c.)/; + expect = /TypeError: (.+\.c is not a function|Cannot find function c.)/; actual = 'No Error'; try { @@ -45,7 +45,7 @@ function test() reportMatch(expect, actual, summary + ': 2'); - expect = /TypeError: (\(let \(b = 1, d = 2\) 2\).c is not a function|Cannot find function c.)/; + expect = /TypeError: (.+\.c is not a function|Cannot find function c.)/; actual = 'No Error'; try { diff --git a/js/src/tests/js1_7/extensions/regress-355052-01.js b/js/src/tests/js1_7/extensions/regress-355052-01.js index 1fbdb69b0fe..42d6fad5ce2 100644 --- a/js/src/tests/js1_7/extensions/regress-355052-01.js +++ b/js/src/tests/js1_7/extensions/regress-355052-01.js @@ -20,7 +20,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - expect = /TypeError: NaN is not a function/; + expect = /TypeError: .+ is not a function/; actual = 'No Error'; try { diff --git a/js/src/tests/js1_7/extensions/regress-355052-02.js b/js/src/tests/js1_7/extensions/regress-355052-02.js index f398e567324..17a6f469bbf 100644 --- a/js/src/tests/js1_7/extensions/regress-355052-02.js +++ b/js/src/tests/js1_7/extensions/regress-355052-02.js @@ -20,7 +20,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - expect = /TypeError: NaN is not a function/; + expect = /TypeError: .+ is not a function/; actual = 'No Error'; try { diff --git a/js/src/tests/js1_7/extensions/regress-355052-03.js b/js/src/tests/js1_7/extensions/regress-355052-03.js index ee04b97d5f0..4e30354689e 100644 --- a/js/src/tests/js1_7/extensions/regress-355052-03.js +++ b/js/src/tests/js1_7/extensions/regress-355052-03.js @@ -20,7 +20,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - expect = /TypeError: NaN is not a function/; + expect = /TypeError: .+ is not a function/; actual = 'No Error'; try { diff --git a/js/src/tests/js1_7/regress/regress-352870-02.js b/js/src/tests/js1_7/regress/regress-352870-02.js index 75eca0cbcf3..6b5db668147 100644 --- a/js/src/tests/js1_7/regress/regress-352870-02.js +++ b/js/src/tests/js1_7/regress/regress-352870-02.js @@ -21,7 +21,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - expect = /TypeError: \[1, 2, 3, 4\].g (has no properties|is undefined)/; + expect = /TypeError: .+\.g (has no properties|is undefined)/; actual = ''; try { diff --git a/js/src/tests/js1_8/extensions/regress-469625.js b/js/src/tests/js1_8/extensions/regress-469625.js index db8b1509ad6..0c7ed4b9de8 100644 --- a/js/src/tests/js1_8/extensions/regress-469625.js +++ b/js/src/tests/js1_8/extensions/regress-469625.js @@ -22,7 +22,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - expect = 'TypeError: [].__proto__ is not a function'; + expect = 'TypeError: (intermediate value).__proto__ is not a function'; jit(true); diff --git a/js/src/tests/js1_8_1/regress/regress-420399.js b/js/src/tests/js1_8_1/regress/regress-420399.js index fe4685bef99..423bfe13c2a 100644 --- a/js/src/tests/js1_8_1/regress/regress-420399.js +++ b/js/src/tests/js1_8_1/regress/regress-420399.js @@ -20,7 +20,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - expect = /TypeError: let \(a = undefined\) a (is undefined|has no properties)/; + expect = "TypeError: undefined has no properties"; try { (let (a=undefined) a).b = 3; @@ -30,7 +30,7 @@ function test() actual = ex + ''; } - reportMatch(expect, actual, summary); + reportCompare(expect, actual, summary); exitFunc ('test'); } diff --git a/js/src/tests/js1_8_5/regress/regress-469758.js b/js/src/tests/js1_8_5/regress/regress-469758.js index 27fa1ed8a75..c8ee0d8e4c3 100644 --- a/js/src/tests/js1_8_5/regress/regress-469758.js +++ b/js/src/tests/js1_8_5/regress/regress-469758.js @@ -9,6 +9,6 @@ try { err = e; } assertEq(err instanceof TypeError, true); -assertEq(err.message, "[][j] is undefined"); +assertEq(err.message, "(intermediate value)[j] is undefined"); reportCompare(0, 0, 'ok');