Bug 767274: New expression decompiler. r=luke

This commit is contained in:
Benjamin Peterson 2012-08-02 09:20:08 -07:00
parent cd12683392
commit cc4d676247
18 changed files with 599 additions and 120 deletions

View File

@ -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");

View File

@ -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");

View File

@ -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;
}

View File

@ -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];

View File

@ -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<jsbytecode **>(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_<BindingVector>(localNames);
}
bool
ExpressionDecompiler::init()
{
if (!sprinter.init())
return false;
localNames = cx->new_<BindingVector>(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<char *>(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

View File

@ -14,7 +14,7 @@ var expect = '';
printBugNumber(BUGNUMBER);
START(summary);
expect = 'TypeError: <x/>.hasOwnProperty is not a constructor';
expect = 'TypeError: (intermediate value).hasOwnProperty is not a constructor';
actual = '';
try

View File

@ -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/))

View File

@ -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];

View File

@ -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) + '';

View File

@ -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 [])])();');

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -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);

View File

@ -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');
}

View File

@ -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');