зеркало из https://github.com/mozilla/gecko-dev.git
Bug Bug 692274, part 4 - Rewrite parsing, emitting and decompiling of let to fix scoping properly (r=jorendorff)
This commit is contained in:
Родитель
0b5000dc38
Коммит
7862c914b8
|
@ -380,6 +380,23 @@ class Vector : private AllocPolicy
|
|||
return *(end() - 1);
|
||||
}
|
||||
|
||||
class Range {
|
||||
friend class Vector;
|
||||
T *cur, *end;
|
||||
Range(T *cur, T *end) : cur(cur), end(end) {}
|
||||
public:
|
||||
Range() {}
|
||||
bool empty() const { return cur == end; }
|
||||
size_t remain() const { return end - cur; }
|
||||
T &front() const { return *cur; }
|
||||
void popFront() { JS_ASSERT(!empty()); ++cur; }
|
||||
T popCopyFront() { JS_ASSERT(!empty()); return *cur++; }
|
||||
};
|
||||
|
||||
Range all() {
|
||||
return Range(begin(), end());
|
||||
}
|
||||
|
||||
/* mutators */
|
||||
|
||||
/* If reserve(length() + N) succeeds, the N next appends are guaranteed to succeed. */
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -942,9 +942,10 @@ enum SrcNoteType {
|
|||
from before loop, else JSOP_NOP at top of
|
||||
do-while loop */
|
||||
SRC_CONTINUE = 5, /* JSOP_GOTO is a continue, not a break;
|
||||
also used on JSOP_ENDINIT if extra comma
|
||||
at end of array literal: [1,2,,];
|
||||
JSOP_DUP continuing destructuring pattern */
|
||||
JSOP_ENDINIT needs extra comma at end of
|
||||
array literal: [1,2,,];
|
||||
JSOP_DUP continuing destructuring pattern;
|
||||
JSOP_POP at end of for-in */
|
||||
SRC_DECL = 6, /* type of a declaration (var, const, let*) */
|
||||
SRC_DESTRUCT = 6, /* JSOP_DUP starting a destructuring assignment
|
||||
operation, with SRC_DECL_* offset operand */
|
||||
|
@ -952,6 +953,8 @@ enum SrcNoteType {
|
|||
next POP, or from CONDSWITCH to first CASE
|
||||
opcode, etc. -- always a forward delta */
|
||||
SRC_GROUPASSIGN = 7, /* SRC_DESTRUCT variant for [a, b] = [c, d] */
|
||||
SRC_DESTRUCTLET = 7, /* JSOP_DUP starting a destructuring let
|
||||
operation, with offset to JSOP_ENTERLET0 */
|
||||
SRC_ASSIGNOP = 8, /* += or another assign-op follows */
|
||||
SRC_COND = 9, /* JSOP_IFEQ is from conditional ?: operator */
|
||||
SRC_BRACE = 10, /* mandatory brace, for scope or to avoid
|
||||
|
@ -1030,6 +1033,8 @@ enum SrcNoteType {
|
|||
#define SN_3BYTE_OFFSET_FLAG 0x80
|
||||
#define SN_3BYTE_OFFSET_MASK 0x7f
|
||||
|
||||
#define SN_MAX_OFFSET ((size_t)((ptrdiff_t)SN_3BYTE_OFFSET_FLAG << 16) - 1)
|
||||
|
||||
#define SN_LENGTH(sn) ((js_SrcNoteSpec[SN_TYPE(sn)].arity == 0) ? 1 \
|
||||
: js_SrcNoteLength(sn))
|
||||
#define SN_NEXT(sn) ((sn) + SN_LENGTH(sn))
|
||||
|
@ -1102,6 +1107,28 @@ BytecodeEmitter::countFinalSourceNotes()
|
|||
return cnt;
|
||||
}
|
||||
|
||||
/*
|
||||
* To avoid offending js_SrcNoteSpec[SRC_DECL].arity, pack the two data needed
|
||||
* to decompile let into one ptrdiff_t:
|
||||
* offset: offset to the LEAVEBLOCK(EXPR) op (not including ENTER/LEAVE)
|
||||
* groupAssign: whether this was an optimized group assign ([x,y] = [a,b])
|
||||
*/
|
||||
inline ptrdiff_t PackLetData(size_t offset, bool groupAssign)
|
||||
{
|
||||
JS_ASSERT(offset <= (size_t(-1) >> 1));
|
||||
return ptrdiff_t(offset << 1) | ptrdiff_t(groupAssign);
|
||||
}
|
||||
|
||||
inline size_t LetDataToOffset(ptrdiff_t w)
|
||||
{
|
||||
return size_t(w) >> 1;
|
||||
}
|
||||
|
||||
inline bool LetDataToGroupAssign(ptrdiff_t w)
|
||||
{
|
||||
return size_t(w) & 1;
|
||||
}
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
struct JSSrcNoteSpec {
|
||||
|
|
|
@ -845,6 +845,11 @@ struct ParseNode {
|
|||
/* Return true if this node appears in a Directive Prologue. */
|
||||
bool isDirectivePrologueMember() const { return pn_prologue; }
|
||||
|
||||
#ifdef JS_HAS_DESTRUCTURING
|
||||
/* Return true if this represents a hole in an array literal. */
|
||||
bool isArrayHole() const { return isKind(PNK_COMMA) && isArity(PN_NULLARY); }
|
||||
#endif
|
||||
|
||||
#ifdef JS_HAS_GENERATOR_EXPRS
|
||||
/*
|
||||
* True if this node is a desugared generator expression.
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -61,6 +61,8 @@ typedef struct BindData BindData;
|
|||
namespace js {
|
||||
|
||||
enum FunctionSyntaxKind { Expression, Statement };
|
||||
enum LetContext { LetExpresion, LetStatement };
|
||||
enum VarContext { HoistVars, DontHoistVars };
|
||||
|
||||
struct Parser : private AutoGCRooter
|
||||
{
|
||||
|
@ -196,7 +198,7 @@ struct Parser : private AutoGCRooter
|
|||
ParseNode *letStatement();
|
||||
#endif
|
||||
ParseNode *expressionStatement();
|
||||
ParseNode *variables(ParseNodeKind kind, bool inLetHead);
|
||||
ParseNode *variables(ParseNodeKind kind, JSObject *blockObj = NULL, VarContext varContext = HoistVars);
|
||||
ParseNode *expr();
|
||||
ParseNode *assignExpr();
|
||||
ParseNode *condExpr1();
|
||||
|
@ -240,7 +242,7 @@ struct Parser : private AutoGCRooter
|
|||
ParseNode *generatorExpr(ParseNode *kid);
|
||||
JSBool argumentList(ParseNode *listNode);
|
||||
ParseNode *bracketedExpr();
|
||||
ParseNode *letBlock(JSBool statement);
|
||||
ParseNode *letBlock(LetContext letContext);
|
||||
ParseNode *returnOrYield(bool useAssignExpr);
|
||||
ParseNode *destructuringExpr(BindData *data, TokenKind tt);
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ f9 = (function() {
|
|||
for each(let w in []) {}
|
||||
}
|
||||
})
|
||||
trap(f9, 22, undefined);
|
||||
trap(f9, 23, undefined);
|
||||
for (b in f9())
|
||||
(function() {})()
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ function f(){
|
|||
this.zzz.zzz;
|
||||
for(let d in []);
|
||||
}
|
||||
trap(f, 18, '')
|
||||
trap(f, 16, '')
|
||||
try {
|
||||
f()
|
||||
} catch(e) {
|
||||
|
|
|
@ -11,5 +11,5 @@ f = (function() {
|
|||
} catch (e) {}
|
||||
}
|
||||
})
|
||||
trap(f, 39, undefined);
|
||||
trap(f, 40, undefined);
|
||||
f()
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
// |jit-test| debug; error: TypeError
|
||||
function f() {
|
||||
""(this.z)
|
||||
}
|
||||
trap(f, 0, '')
|
||||
f()
|
|
@ -0,0 +1,7 @@
|
|||
function f() {
|
||||
var ss = [new f("abc"), new String("foobar"), new String("quux")];
|
||||
for (let a6 = this ;; ) {}
|
||||
}
|
||||
try {
|
||||
f();
|
||||
} catch (e) {}
|
|
@ -0,0 +1,16 @@
|
|||
var x = -false;
|
||||
switch(x) {
|
||||
case 11:
|
||||
let y = 42;
|
||||
}
|
||||
switch(x) {
|
||||
case 11:
|
||||
let y = 42;
|
||||
let z = 'ponies';
|
||||
}
|
||||
switch(x) {
|
||||
case 11:
|
||||
let y = 42;
|
||||
let z = 'ponies';
|
||||
let a = false;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
// |jit-test| error: TypeError
|
||||
var obj = {};
|
||||
let ([] = print) 3;
|
||||
let ( i = "a" ) new i [ obj[i] ];
|
|
@ -0,0 +1,12 @@
|
|||
Function.prototype.X = 42;
|
||||
function ownProperties() {
|
||||
var props = {};
|
||||
var r = function () {};
|
||||
for (var a in r) {
|
||||
let (a = function() { for (var r=0;r<6;++r) ++a; }) {
|
||||
a();
|
||||
}
|
||||
props[a] = true;
|
||||
}
|
||||
}
|
||||
ownProperties();
|
|
@ -0,0 +1,9 @@
|
|||
test();
|
||||
function test() {
|
||||
var f;
|
||||
f = function() { (let(x) {y: z}) }
|
||||
let (f = function() {
|
||||
for (var t=0;t<6;++t) ++f;
|
||||
}) { f(); } // { }
|
||||
actual = f + '';
|
||||
}
|
|
@ -0,0 +1,342 @@
|
|||
function test(str, arg, result)
|
||||
{
|
||||
arg = arg || 'ponies';
|
||||
result = result || 'ponies';
|
||||
|
||||
var fun = new Function('x', str);
|
||||
|
||||
var got = fun.toSource().replace(/\n/g,'');
|
||||
var expect = '(function anonymous(x) {' + str + '})';
|
||||
if (got !== expect) {
|
||||
print("GOT: " + got);
|
||||
print("EXPECT: " + expect);
|
||||
assertEq(got, expect);
|
||||
}
|
||||
|
||||
Reflect.parse(got);
|
||||
|
||||
var got = fun(arg);
|
||||
var expect = result;
|
||||
if (got !== expect) {
|
||||
print("GOT:" + got);
|
||||
print("EXPECT: " + expect);
|
||||
assertEq(got, expect);
|
||||
}
|
||||
}
|
||||
|
||||
function isError(str)
|
||||
{
|
||||
var caught = false;
|
||||
try {
|
||||
new Function(str);
|
||||
} catch(e) {
|
||||
assertEq(String(e).indexOf('TypeError') == 0 || String(e).indexOf('SyntaxError') == 0, true);
|
||||
caught = true;
|
||||
}
|
||||
assertEq(caught, true);
|
||||
}
|
||||
|
||||
// let expr
|
||||
test('return let (y) x;');
|
||||
test('return let (x) "" + x;', 'unicorns', 'undefined');
|
||||
test('return let (y = x) (y++, "" + y);', 'unicorns', 'NaN');
|
||||
test('return let (y = 1) (y = x, y);');
|
||||
test('return let ([] = x) x;');
|
||||
test('return let (x = {a: x}) x.a;');
|
||||
test('return let ({a: x} = {a: x}) x;');
|
||||
test('return let ([x] = {0: x}) x;');
|
||||
test('return let ({0: x} = [x]) x;');
|
||||
test('return let ({0: []} = []) x;');
|
||||
test('return let ([, ] = x) x;');
|
||||
test('return let ([, , , , ] = x) x;');
|
||||
test('return let ([[]] = x) x;');
|
||||
test('return let ([[[[[[[[[[[[[]]]]]]]]]]]]] = x) x;');
|
||||
test('return let ([[], []] = x) x;');
|
||||
test('return let ([[[[]]], [], , [], [[]]] = x) x;');
|
||||
test('return let ({x: []} = x) x;');
|
||||
test('return let ({x: [], y: {x: []}} = x) "ponies";', {y:{}});
|
||||
test('return let ({x: []} = x, [{x: []}] = x) "ponies";');
|
||||
test('return let (x = x) x;');
|
||||
test('return let (x = eval("x")) x;');
|
||||
test('return let (x = (let (x = x + 1) x) + 1) x;', 1, 3);
|
||||
test('return let (x = (let (x = eval("x") + 1) eval("x")) + 1) eval("x");', 1, 3);
|
||||
test('return let (x = x + 1, y = x) y;');
|
||||
test('return let (x = x + 1, [] = x, [[, , ]] = x, y = x) y;');
|
||||
test('return let ([{a: x}] = x, [, {b: y}] = x) let (x = x + 1, y = y + 2) x + y;', [{a:"p"},{b:"p"}], "p1p2");
|
||||
test('return let ([] = []) x;');
|
||||
test('return let ([] = [x]) x;');
|
||||
test('return let ([x] = [x]) x;');
|
||||
test('return let ([[a, [b, c]]] = [[x, []]]) a;');
|
||||
test('return let ([x, y] = [x, x + 1]) x + y;', 1, 3);
|
||||
test('return let ([x, y, z] = [x, x + 1, x + 2]) x + y + z;', 1, 6);
|
||||
test('return let ([[x]] = [[x]]) x;');
|
||||
test('return let ([x, y] = [x, x + 1]) x;');
|
||||
test('return let ([x, [y, z]] = [x, x + 1]) x;');
|
||||
test('return let ([{x: [x]}, {y1: y, z1: z}] = [x, x + 1]) x;',{x:['ponies']});
|
||||
test('return let (x = (3, x)) x;');
|
||||
test('return let (x = x + "s") x;', 'ponie');
|
||||
test('return let ([x] = (3, [x])) x;');
|
||||
test('return let ([] = [[]] = {}) x;');
|
||||
test('return let (y = x) function () {return eval("y");}();');
|
||||
test('return eval("let (y = x) y");');
|
||||
test('return let (y = x) (eval("var y = 2"), y);', 'ponies', 2);
|
||||
test('"use strict";return let (y = x) (eval("var y = 2"), y);');
|
||||
test('this.y = x;return let (y = 1) this.eval("y");');
|
||||
test('try {let (x = x) eval("throw x");} catch (e) {return e;}');
|
||||
test('try {return let (x = eval("throw x")) x;} catch (e) {return e;}');
|
||||
isError('let (x = 1, x = 2) x');
|
||||
isError('let ([x, y] = a, {a:x} = b) x');
|
||||
isError('let ([x, y, x] = a) x');
|
||||
isError('let ([x, [y, [x]]] = a) x');
|
||||
isError('let (x = function() { return x}) x()return x;');
|
||||
isError('(let (x = function() { return x}) x())return x;');
|
||||
|
||||
// let block
|
||||
test('let (y) {return x;}');
|
||||
test('let (y = x) {y++;return "" + y;}', 'unicorns', 'NaN');
|
||||
test('let (y = 1) {y = x;return y;}');
|
||||
test('let (x) {return "" + x;}', 'unicorns', 'undefined');
|
||||
test('let ([] = x) {return x;}');
|
||||
test('let (x) {}return x;');
|
||||
test('let (x = {a: x}) {return x.a;}');
|
||||
test('let ({a: x} = {a: x}) {return x;}');
|
||||
test('let ([x] = {0: x}) {return x;}');
|
||||
test('let ({0: x} = [x]) {return x;}');
|
||||
test('let ({0: []} = []) {return x;}');
|
||||
test('let ([, ] = x) {return x;}');
|
||||
test('let ([, , , , ] = x) {return x;}');
|
||||
test('let ([[]] = x) {return x;}');
|
||||
test('let ([[[[[[[[[[[[[]]]]]]]]]]]]] = x) {return x;}');
|
||||
test('let ([[], []] = x) {return x;}');
|
||||
test('let ([[[[]]], [], , [], [[]]] = x) {return x;}');
|
||||
test('let ({x: []} = x) {return x;}');
|
||||
test('let ({x: [], y: {x: []}} = x) {return "ponies";}', {y:{}});
|
||||
test('let ({x: []} = x, [{x: []}] = x) {return "ponies";}');
|
||||
test('let (x = x) {return x;}');
|
||||
test('let (x = eval("x")) {return x;}');
|
||||
test('let (x = (let (x = x + 1) x) + 1) {return x;}', 1, 3);
|
||||
test('let (x = (let (x = eval("x") + 1) eval("x")) + 1) {return eval("x");}', 1, 3);
|
||||
test('let (x = x + 1, y = x) {return y;}');
|
||||
test('let (x = x + 1, [] = x, [[, , ]] = x, y = x) {return y;}');
|
||||
test('let ([{a: x}] = x, [, {b: y}] = x) {let (x = x + 1, y = y + 2) {return x + y;}}', [{a:"p"},{b:"p"}], "p1p2");
|
||||
test('let ([] = []) {return x;}');
|
||||
test('let ([] = [x]) {return x;}');
|
||||
test('let ([x] = [x]) {return x;}');
|
||||
test('let ([[a, [b, c]]] = [[x, []]]) {return a;}');
|
||||
test('let ([x, y] = [x, x + 1]) {return x + y;}', 1, 3);
|
||||
test('let ([x, y, z] = [x, x + 1, x + 2]) {return x + y + z;}', 1, 6);
|
||||
test('let ([[x]] = [[x]]) {return x;}');
|
||||
test('let ([x, y] = [x, x + 1]) {return x;}');
|
||||
test('let ([x, [y, z]] = [x, x + 1]) {return x;}');
|
||||
test('let ([{x: [x]}, {y1: y, z1: z}] = [x, x + 1]) {return x;}',{x:['ponies']});
|
||||
test('let (y = x[1]) {let (x = x[0]) {try {let (y = "unicorns") {throw y;}} catch (e) {return x + y;}}}', ['pon','ies']);
|
||||
test('let (x = x) {try {let (x = "unicorns") eval("throw x");} catch (e) {return x;}}');
|
||||
test('let ([] = [[]] = {}) {return x;}');
|
||||
test('let (y = x) {return function () {return eval("y");}();}');
|
||||
test('return eval("let (y = x) {y;}");');
|
||||
test('let (y = x) {eval("var y = 2");return y;}', 'ponies', 2);
|
||||
test('"use strict";let (y = x) {eval("var y = 2");return y;}');
|
||||
test('this.y = x;let (y = 1) {return this.eval("y");}');
|
||||
isError('let (x = 1, x = 2) {x}');
|
||||
isError('let ([x, y] = a, {a:x} = b) {x}');
|
||||
isError('let ([x, y, x] = a) {x}');
|
||||
isError('let ([x, [y, [x]]] = a) {x}');
|
||||
|
||||
// var declarations
|
||||
test('var y;return x;');
|
||||
test('var y = x;return x;');
|
||||
test('var [] = x;return x;');
|
||||
test('var [, ] = x;return x;');
|
||||
test('var [, , , , ] = x;return x;');
|
||||
test('var [[]] = x;return x;');
|
||||
test('var [[[[[[[[[[[[[]]]]]]]]]]]]] = x;return x;');
|
||||
test('var [[], []] = x;return x;');
|
||||
test('var [[[[]]], [], , [], [[]]] = x;return x;');
|
||||
test('var {x: []} = x;return x;');
|
||||
test('var {x: [], y: {x: []}} = x;return "ponies";', {y:{}});
|
||||
test('var {x: []} = x, [{x: []}] = x;return "ponies";');
|
||||
test('var x = x;return x;');
|
||||
test('var y = y;return "" + y;', 'unicorns', 'undefined');
|
||||
test('var x = eval("x");return x;');
|
||||
test('var x = (let (x = x + 1) x) + 1;return x;', 1, 3);
|
||||
test('var x = (let (x = eval("x") + 1) eval("x")) + 1;return eval("x");', 1, 3);
|
||||
test('var X = x + 1, y = x;return y;');
|
||||
test('var X = x + 1, [] = X, [[, , ]] = X, y = x;return y;');
|
||||
test('var [{a: X}] = x, [, {b: y}] = x;var X = X + 1, y = y + 2;return X + y;', [{a:"p"},{b:"p"}], "p1p2");
|
||||
test('var [x] = [x];return x;');
|
||||
test('var [[a, [b, c]]] = [[x, []]];return a;');
|
||||
test('var [y] = [x];return y;');
|
||||
test('var [x, y] = [x, x + 1];return x + y;', 1, 3);
|
||||
test('var [x, y, z] = [x, x + 1, x + 2];return x + y + z;', 1, 6);
|
||||
test('var [[x]] = [[x]];return x;');
|
||||
test('var [x, y] = [x, x + 1];return x;');
|
||||
test('var [x, [y, z]] = [x, x + 1];return x;');
|
||||
test('var [{x: [x]}, {y1: y, z1: z}] = [x, x + 1];return x;',{x:['ponies']});
|
||||
test('var [] = [[]] = {};return x;');
|
||||
test('if (x) {var y = x;return x;}');
|
||||
test('if (x) {y = x;var y = y;return y;}');
|
||||
test('if (x) {var z = y;var [y] = x;z += y;}return z;', ['-'], 'undefined-');
|
||||
|
||||
// let declaration in context
|
||||
test('if (x) {let y;return x;}');
|
||||
test('if (x) {let x;return "" + x;}', 'unicorns', 'undefined');
|
||||
test('if (x) {let y = x;return x;}');
|
||||
test('if (x) {y = x;let y = y;return y;}');
|
||||
test('if (x) {var z = y;let [y] = x;z += y;}return z;', ['-'], 'undefined-');
|
||||
test('if (x) {let y = x;return x;}');
|
||||
test('if (x) {let [] = x;return x;}');
|
||||
test('if (x) {let [, ] = x;return x;}');
|
||||
test('if (x) {let [, , , , ] = x;return x;}');
|
||||
test('if (x) {let [[]] = x;return x;}');
|
||||
test('if (x) {let [[[[[[[[[[[[[]]]]]]]]]]]]] = x;return x;}');
|
||||
test('if (x) {let [[], []] = x;return x;}');
|
||||
test('if (x) {let [[[[]]], [], , [], [[]]] = x;return x;}');
|
||||
test('if (x) {let {x: []} = x;return x;}');
|
||||
test('if (x) {let {x: [], y: {x: []}} = x;return "ponies";}', {y:{}});
|
||||
test('if (x) {let {x: []} = x, [{x: []}] = x;return "ponies";}');
|
||||
test('if (x) {let x = x;return "" + x;}', 'unicorns', 'undefined');
|
||||
test('if (x) {let y = y;return "" + y;}', 'unicorns', 'undefined');
|
||||
test('if (x) {let x = eval("x");return "" + x;}', 'unicorns', 'undefined');
|
||||
test('if (x) {let y = (let (x = x + 1) x) + 1;return y;}', 1, 3);
|
||||
test('if (x) {let y = (let (x = eval("x") + 1) eval("x")) + 1;return eval("y");}', 1, 3);
|
||||
test('if (x) {let X = x + 1, y = x;return y;}');
|
||||
test('if (x) {let X = x + 1, [] = X, [[, , ]] = X, y = x;return y;}');
|
||||
test('if (x) {let [{a: X}] = x, [, {b: Y}] = x;var XX = X + 1, YY = Y + 2;return XX + YY;}', [{a:"p"},{b:"p"}], "p1p2");
|
||||
test('if (x) {let [[a, [b, c]]] = [[x, []]];return a;}');
|
||||
test('if (x) {let [X] = [x];return X;}');
|
||||
test('if (x) {let [y] = [x];return y;}');
|
||||
test('if (x) {let [X, y] = [x, x + 1];return X + y;}', 1, 3);
|
||||
test('if (x) {let [X, y, z] = [x, x + 1, x + 2];return X + y + z;}', 1, 6);
|
||||
test('if (x) {let [[X]] = [[x]];return X;}');
|
||||
test('if (x) {let [X, y] = [x, x + 1];return X;}');
|
||||
test('if (x) {let [X, [y, z]] = [x, x + 1];return X;}');
|
||||
test('if (x) {let [{x: [X]}, {y1: y, z1: z}] = [x, x + 1];return X;}',{x:['ponies']});
|
||||
test('if (x) {let y = x;try {let x = 1;throw 2;} catch (e) {return y;}}');
|
||||
test('if (x) {let [] = [[]] = {};return x;}');
|
||||
test('let (y, [] = x) {}try {let a = b(), b;} catch (e) {return x;}');
|
||||
test('try {let x = 1;throw 2;} catch (e) {return x;}');
|
||||
test('let (y = x) {let x;return y;}');
|
||||
test('let (y = x) {let x = y;return x;}');
|
||||
test('let ([y, z] = x) {let a = x, b = y;return a;}');
|
||||
test('let ([y, z] = x, a = x, [] = x) {let b = x, c = y;return a;}');
|
||||
test('function f() {return unicorns;}try {let (x = 1) {let a, b;f();}} catch (e) {return x;}');
|
||||
test('function f() {return unicorns;}try {let (x = 1) {let a, b;}f();} catch (e) {return x;}');
|
||||
test('x.foo;{let y = x;return y;}');
|
||||
test('x.foo;if (x) {x.bar;let y = x;return y;}');
|
||||
test('if (x) {let y = x;return function () {return eval("y");}();}');
|
||||
test('return eval("let y = x; y");');
|
||||
test('if (x) {let y = x;eval("var y = 2");return y;}', 'ponies', 2);
|
||||
test('"use strict";if (x) {let y = x;eval("var y = 2");return y;}');
|
||||
test('"use strict";if (x) {let y = x;eval("let y = 2");return y;}');
|
||||
test('"use strict";if (x) {let y = 1;return eval("let y = x;y;");}');
|
||||
test('this.y = x;if (x) {let y = 1;return this.eval("y");}');
|
||||
isError('if (x) {let (x = 1, x = 2) {x}}');
|
||||
isError('if (x) {let ([x, y] = a, {a:x} = b) {x}}');
|
||||
isError('if (x) {let ([x, y, x] = a) {x}}');
|
||||
isError('if (x) {let ([x, [y, [x]]] = a) {x}}');
|
||||
isError('let ([x, y] = x) {let x;}');
|
||||
|
||||
// for(;;)
|
||||
test('for (;;) {return x;}');
|
||||
test('for (let y = 1;;) {return x;}');
|
||||
test('for (let y = 1;; ++y) {return x;}');
|
||||
test('for (let y = 1; ++y;) {return x;}');
|
||||
test('for (let (x = 1) x; x != 1; ++x) {return x;}');
|
||||
test('for (let [, {a: [], b: []}] = x, [] = x; x;) {return x;}');
|
||||
test('for (let x = 1, [y, z] = x, a = x; z < 4; ++z) {return x + y;}', [2,3], 3);
|
||||
test('for (let (x = 1, [{a: b, c: d}] = [{a: 1, c: 2}]) x; x != 1; ++x) {return x;}');
|
||||
test('for (let [[a, [b, c]]] = [[x, []]];;) {return a;}');
|
||||
test('var sum = 0;for (let y = x; y < 4; ++y) {sum += y;}return sum;', 1, 6);
|
||||
test('var sum = 0;for (let x = x, y = 10; x < 4; ++x) {sum += x;}return sum;', 1, 6);
|
||||
test('var sum = 0;for (let x = x; x < 4; ++x) {sum += x;}return x;', 1, 1);
|
||||
test('var sum = 0;for (let x = eval("x"); x < 4; ++x) {sum += x;}return sum;', 1, 6);
|
||||
test('var sum = 0;for (let x = x; eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6);
|
||||
test('var sum = 0;for (let x = eval("x"); eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6);
|
||||
test('for (var y = 1;;) {return x;}');
|
||||
test('for (var y = 1;; ++y) {return x;}');
|
||||
test('for (var y = 1; ++y;) {return x;}');
|
||||
test('for (var [, {a: [], b: []}] = x, [] = x; x;) {return x;}');
|
||||
test('for (var X = 1, [y, z] = x, a = x; z < 4; ++z) {return X + y;}', [2,3], 3);
|
||||
test('var sum = 0;for (var y = x; y < 4; ++y) {sum += y;}return sum;', 1, 6);
|
||||
test('var sum = 0;for (var X = x, y = 10; X < 4; ++X) {sum += X;}return sum;', 1, 6);
|
||||
test('var sum = 0;for (var X = x; X < 4; ++X) {sum += X;}return x;', 1, 1);
|
||||
test('var sum = 0;for (var X = eval("x"); X < 4; ++X) {sum += X;}return sum;', 1, 6);
|
||||
test('var sum = 0;for (var X = x; eval("X") < 4; ++X) {sum += eval("X");}return sum;', 1, 6);
|
||||
test('var sum = 0;for (var X = eval("x"); eval("X") < 4; ++X) {sum += eval("X");}return sum;', 1, 6);
|
||||
test('try {for (let x = eval("throw x");;) {}} catch (e) {return e;}');
|
||||
test('try {for (let x = x + "s"; eval("throw x");) {}} catch (e) {return e;}', 'ponie');
|
||||
test('for (let y = x;;) {let x;return y;}');
|
||||
test('for (let y = x;;) {let y;return x;}');
|
||||
test('for (let y;;) {let y;return x;}');
|
||||
test('for (let a = x;;) {let c = x, d = x;return c;}');
|
||||
test('for (let [a, b] = x;;) {let c = x, d = x;return c;}');
|
||||
test('for (let [] = [[]] = {};;) {return x;}');
|
||||
isError('for (let x = 1, x = 2;;) {}');
|
||||
isError('for (let [x, y] = a, {a:x} = b;;) {}');
|
||||
isError('for (let [x, y, x] = a;;) {}');
|
||||
isError('for (let [x, [y, [x]]] = a;;) {}');
|
||||
|
||||
// for(in)
|
||||
test('for (let i in x) {return x;}');
|
||||
test('for (let i in x) {let y;return x;}');
|
||||
test('for each (let [a, b] in x) {let y;return x;}');
|
||||
test('for (let i in x) {let (i = x) {return i;}}');
|
||||
test('for (let i in x) {let i = x;return i;}');
|
||||
test('for each (let [x, y] in x) {return x + y;}', [['ponies', '']]);
|
||||
test('for each (let [{0: x, 1: y}, z] in x) {return x + y + z;}', [[['po','nies'], '']]);
|
||||
test('var s = "";for (let a in x) {for (let b in x) {s += a + b;}}return s;', [1,2], '00011011');
|
||||
test('var res = "";for (let i in x) {res += x[i];}return res;');
|
||||
test('var res = "";for (var i in x) {res += x[i];}return res;');
|
||||
test('for each (let {x: y, y: x} in [{x: x, y: x}]) {return y;}');
|
||||
test('for (let x in eval("x")) {return x;}', {ponies:true});
|
||||
test('for (let x in x) {return eval("x");}', {ponies:true});
|
||||
test('for (let x in eval("x")) {return eval("x");}', {ponies:true});
|
||||
test('for ((let (x = {y: true}) x).y in eval("x")) {return eval("x");}');
|
||||
test('for (let i in x) {break;}return x;');
|
||||
test('for (let i in x) {break;}return eval("x");');
|
||||
test('for (let x in x) {break;}return x;');
|
||||
test('for (let x in x) {break;}return eval("x");');
|
||||
test('a:for (let i in x) {for (let j in x) {break a;}}return x;');
|
||||
test('a:for (let i in x) {for (let j in x) {break a;}}return eval("x");');
|
||||
test('var j;for (let i in x) {j = i;break;}return j;', {ponies:true});
|
||||
test('try {for (let x in eval("throw x")) {}} catch (e) {return e;}');
|
||||
test('try {for each (let x in x) {eval("throw x");}} catch (e) {return e;}', ['ponies']);
|
||||
isError('for (let [x, x] in o) {}');
|
||||
isError('for (let [x, y, x] in o) {}');
|
||||
isError('for (let [x, [y, [x]]] in o) {}');
|
||||
|
||||
// genexps
|
||||
test('return (i for (i in x)).next();', {ponies:true});
|
||||
test('return (eval("i") for (i in x)).next();', {ponies:true});
|
||||
test('return (eval("i") for (i in eval("x"))).next();', {ponies:true});
|
||||
test('try {return (eval("throw i") for (i in x)).next();} catch (e) {return e;}', {ponies:true});
|
||||
|
||||
// array comprehension
|
||||
test('return [i for (i in x)][0];', {ponies:true});
|
||||
test('return [eval("i") for (i in x)][0];', {ponies:true});
|
||||
test('return [eval("i") for (i in eval("x"))][0];', {ponies:true});
|
||||
test('try {return [eval("throw i") for (i in x)][0];} catch (e) {return e;}', {ponies:true});
|
||||
|
||||
// don't forget about switch craziness
|
||||
test('var y = 3;switch (function () {return eval("y");}()) {case 3:let y;return x;default:;}');
|
||||
test('switch (x) {case 3:let y;return 3;case 4:let z;return 4;default:return x;}');
|
||||
test('switch (x) {case 3:let x;break;default:if (x === undefined) {return "ponies";}}');
|
||||
test('switch (x) {case 3:default:let y;let (y = x) {return y;}}');
|
||||
isError('switch (x) {case 3:let y;return 3;case 4:let y;return 4;default:;}');
|
||||
|
||||
// test weird cases where the decompiler changes tokens
|
||||
function testWeird(str, printedAs, arg, result)
|
||||
{
|
||||
var fun = new Function('x', str);
|
||||
|
||||
// this is lame and doesn't normalize whitespace so if an assert fails
|
||||
// here, see if its just whitespace and fix the caller
|
||||
assertEq(fun.toSource(), '(function anonymous(x) {' + printedAs + '})');
|
||||
|
||||
test(printedAs, arg, result);
|
||||
}
|
||||
|
||||
testWeird('let y = x;return x;', 'var y = x;return x;');
|
||||
testWeird('let y = 1, y = x;return y;', 'var y = 1, y = x;return y;');
|
||||
testWeird('return let ({x:x, y:y} = x) x + y', 'return let ({x, y} = x) x + y;', {x:'pon', y:'ies'});
|
||||
testWeird('let ({x:x, y:y} = x) {return x + y;}', 'let ({x, y} = x) {return x + y;}', {x:'pon', y:'ies'});
|
|
@ -119,11 +119,11 @@ ScriptAnalysis::checkAliasedName(JSContext *cx, jsbytecode *pc)
|
|||
|
||||
JSAtom *atom;
|
||||
if (JSOp(*pc) == JSOP_DEFFUN) {
|
||||
JSFunction *fun = script->getFunction(js_GetIndexFromBytecode(cx, script, pc, 0));
|
||||
JSFunction *fun = script->getFunction(js_GetIndexFromBytecode(script, pc, 0));
|
||||
atom = fun->atom;
|
||||
} else {
|
||||
JS_ASSERT(JOF_TYPE(js_CodeSpec[*pc].format) == JOF_ATOM);
|
||||
atom = script->getAtom(js_GetIndexFromBytecode(cx, script, pc, 0));
|
||||
atom = script->getAtom(js_GetIndexFromBytecode(script, pc, 0));
|
||||
}
|
||||
|
||||
uintN index;
|
||||
|
@ -389,6 +389,8 @@ ScriptAnalysis::analyzeBytecode(JSContext *cx)
|
|||
isInlineable = canTrackVars = false;
|
||||
break;
|
||||
|
||||
case JSOP_ENTERLET0:
|
||||
case JSOP_ENTERLET1:
|
||||
case JSOP_ENTERBLOCK:
|
||||
case JSOP_LEAVEBLOCK:
|
||||
addsScopeObjects_ = true;
|
||||
|
|
|
@ -203,9 +203,6 @@ GetDefCount(JSScript *script, unsigned offset)
|
|||
JS_ASSERT(offset < script->length);
|
||||
jsbytecode *pc = script->code + offset;
|
||||
|
||||
if (js_CodeSpec[*pc].ndefs == -1)
|
||||
return js_GetEnterBlockStackDefs(NULL, script, pc);
|
||||
|
||||
/*
|
||||
* Add an extra pushed value for OR/AND opcodes, so that they are included
|
||||
* in the pushed array of stack values for type inference.
|
||||
|
@ -227,7 +224,7 @@ GetDefCount(JSScript *script, unsigned offset)
|
|||
*/
|
||||
return (pc[1] + 1);
|
||||
default:
|
||||
return js_CodeSpec[*pc].ndefs;
|
||||
return StackDefs(script, pc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,7 +237,7 @@ GetUseCount(JSScript *script, unsigned offset)
|
|||
if (JSOp(*pc) == JSOP_PICK)
|
||||
return (pc[1] + 1);
|
||||
if (js_CodeSpec[*pc].nuses == -1)
|
||||
return js_GetVariableStackUses(JSOp(*pc), pc);
|
||||
return StackUses(script, pc);
|
||||
return js_CodeSpec[*pc].nuses;
|
||||
}
|
||||
|
||||
|
|
|
@ -2032,21 +2032,21 @@ TypeCompartment::newAllocationSiteTypeObject(JSContext *cx, const AllocationSite
|
|||
static inline jsid
|
||||
GetAtomId(JSContext *cx, JSScript *script, const jsbytecode *pc, unsigned offset)
|
||||
{
|
||||
unsigned index = js_GetIndexFromBytecode(cx, script, (jsbytecode*) pc, offset);
|
||||
unsigned index = js_GetIndexFromBytecode(script, (jsbytecode*) pc, offset);
|
||||
return MakeTypeId(cx, ATOM_TO_JSID(script->getAtom(index)));
|
||||
}
|
||||
|
||||
static inline JSObject *
|
||||
GetScriptObject(JSContext *cx, JSScript *script, const jsbytecode *pc, unsigned offset)
|
||||
{
|
||||
unsigned index = js_GetIndexFromBytecode(cx, script, (jsbytecode*) pc, offset);
|
||||
unsigned index = js_GetIndexFromBytecode(script, (jsbytecode*) pc, offset);
|
||||
return script->getObject(index);
|
||||
}
|
||||
|
||||
static inline const Value &
|
||||
GetScriptConst(JSContext *cx, JSScript *script, const jsbytecode *pc)
|
||||
{
|
||||
unsigned index = js_GetIndexFromBytecode(cx, script, (jsbytecode*) pc, 0);
|
||||
unsigned index = js_GetIndexFromBytecode(script, (jsbytecode*) pc, 0);
|
||||
return script->getConst(index);
|
||||
}
|
||||
|
||||
|
@ -3956,6 +3956,7 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
|
|||
|
||||
case JSOP_ENTERWITH:
|
||||
case JSOP_ENTERBLOCK:
|
||||
case JSOP_ENTERLET0:
|
||||
/*
|
||||
* Scope lookups can occur on the values being pushed here. We don't track
|
||||
* the value or its properties, and just monitor all name opcodes in the
|
||||
|
@ -3963,6 +3964,16 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
|
|||
*/
|
||||
break;
|
||||
|
||||
case JSOP_ENTERLET1:
|
||||
/*
|
||||
* JSOP_ENTERLET1 enters a let block with an unrelated value on top of
|
||||
* the stack (such as the condition to a switch) whose constraints must
|
||||
* be propagated. The other values are ignored for the same reason as
|
||||
* JSOP_ENTERLET0.
|
||||
*/
|
||||
poppedTypes(pc, 0)->addSubset(cx, &pushed[defCount - 1]);
|
||||
break;
|
||||
|
||||
case JSOP_ITER: {
|
||||
/*
|
||||
* Use a per-script type set to unify the possible target types of all
|
||||
|
@ -4021,6 +4032,9 @@ ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset,
|
|||
poppedTypes(pc, 0)->addSubset(cx, &pushed[0]);
|
||||
break;
|
||||
|
||||
case JSOP_LEAVEFORLETIN:
|
||||
break;
|
||||
|
||||
case JSOP_CASE:
|
||||
case JSOP_CASEX:
|
||||
poppedTypes(pc, 1)->addSubset(cx, &pushed[0]);
|
||||
|
@ -4554,7 +4568,7 @@ AnalyzeNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun, JSO
|
|||
* integer properties and bail out. We can't mark the aggregate
|
||||
* JSID_VOID type property as being in a definite slot.
|
||||
*/
|
||||
unsigned index = js_GetIndexFromBytecode(cx, script, pc, 0);
|
||||
unsigned index = js_GetIndexFromBytecode(script, pc, 0);
|
||||
jsid id = ATOM_TO_JSID(script->getAtom(index));
|
||||
if (MakeTypeId(cx, id) != id)
|
||||
return false;
|
||||
|
@ -5421,6 +5435,8 @@ IgnorePushed(const jsbytecode *pc, unsigned index)
|
|||
/* Storage for 'with' and 'let' blocks not monitored. */
|
||||
case JSOP_ENTERWITH:
|
||||
case JSOP_ENTERBLOCK:
|
||||
case JSOP_ENTERLET0:
|
||||
case JSOP_ENTERLET1:
|
||||
return true;
|
||||
|
||||
/* We don't keep track of the iteration state for 'for in' or 'for each in' loops. */
|
||||
|
|
|
@ -1857,9 +1857,6 @@ js::Interpret(JSContext *cx, StackFrame *entryFrame, InterpMode interpMode)
|
|||
/* No-ops for ease of decompilation. */
|
||||
ADD_EMPTY_CASE(JSOP_NOP)
|
||||
ADD_EMPTY_CASE(JSOP_UNUSED0)
|
||||
ADD_EMPTY_CASE(JSOP_UNUSED1)
|
||||
ADD_EMPTY_CASE(JSOP_UNUSED2)
|
||||
ADD_EMPTY_CASE(JSOP_UNUSED3)
|
||||
ADD_EMPTY_CASE(JSOP_CONDSWITCH)
|
||||
ADD_EMPTY_CASE(JSOP_TRY)
|
||||
#if JS_HAS_XML_SUPPORT
|
||||
|
@ -5109,16 +5106,28 @@ END_CASE(JSOP_GETFUNNS)
|
|||
#endif /* JS_HAS_XML_SUPPORT */
|
||||
|
||||
BEGIN_CASE(JSOP_ENTERBLOCK)
|
||||
BEGIN_CASE(JSOP_ENTERLET0)
|
||||
BEGIN_CASE(JSOP_ENTERLET1)
|
||||
{
|
||||
JSObject *obj;
|
||||
LOAD_OBJECT(0, obj);
|
||||
JS_ASSERT(obj->isStaticBlock());
|
||||
JS_ASSERT(regs.fp()->maybeBlockChain() == obj->staticBlockScopeChain());
|
||||
|
||||
if (op == JSOP_ENTERBLOCK) {
|
||||
JS_ASSERT(regs.fp()->base() + OBJ_BLOCK_DEPTH(cx, obj) == regs.sp);
|
||||
Value *vp = regs.sp + OBJ_BLOCK_COUNT(cx, obj);
|
||||
JS_ASSERT(regs.sp < vp);
|
||||
JS_ASSERT(vp <= regs.fp()->slots() + script->nslots);
|
||||
SetValueRangeToUndefined(regs.sp, vp);
|
||||
regs.sp = vp;
|
||||
} else if (op == JSOP_ENTERLET0) {
|
||||
JS_ASSERT(regs.fp()->base() + OBJ_BLOCK_DEPTH(cx, obj) + OBJ_BLOCK_COUNT(cx, obj)
|
||||
== regs.sp);
|
||||
} else if (op == JSOP_ENTERLET1) {
|
||||
JS_ASSERT(regs.fp()->base() + OBJ_BLOCK_DEPTH(cx, obj) + OBJ_BLOCK_COUNT(cx, obj)
|
||||
== regs.sp - 1);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
JS_ASSERT(regs.fp()->maybeBlockChain() == obj->staticBlockScopeChain());
|
||||
|
@ -5134,7 +5143,8 @@ BEGIN_CASE(JSOP_ENTERBLOCK)
|
|||
while (obj2->isWith())
|
||||
obj2 = obj2->internalScopeChain();
|
||||
if (obj2->isBlock() &&
|
||||
obj2->getPrivate() == js_FloatingFrameIfGenerator(cx, regs.fp())) {
|
||||
obj2->getPrivate() == js_FloatingFrameIfGenerator(cx, regs.fp()))
|
||||
{
|
||||
JSObject *youngestProto = obj2->getProto();
|
||||
JS_ASSERT(youngestProto->isStaticBlock());
|
||||
JSObject *parent = obj;
|
||||
|
@ -5147,14 +5157,14 @@ BEGIN_CASE(JSOP_ENTERBLOCK)
|
|||
}
|
||||
END_CASE(JSOP_ENTERBLOCK)
|
||||
|
||||
BEGIN_CASE(JSOP_LEAVEBLOCKEXPR)
|
||||
BEGIN_CASE(JSOP_LEAVEBLOCK)
|
||||
BEGIN_CASE(JSOP_LEAVEFORLETIN)
|
||||
BEGIN_CASE(JSOP_LEAVEBLOCKEXPR)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
JS_ASSERT(regs.fp()->blockChain().isStaticBlock());
|
||||
uintN blockDepth = OBJ_BLOCK_DEPTH(cx, ®s.fp()->blockChain());
|
||||
DebugOnly<uintN> blockDepth = OBJ_BLOCK_DEPTH(cx, ®s.fp()->blockChain());
|
||||
JS_ASSERT(blockDepth <= StackDepth(script));
|
||||
#endif
|
||||
|
||||
/*
|
||||
* If we're about to leave the dynamic scope of a block that has been
|
||||
* cloned onto fp->scopeChain, clear its private data, move its locals from
|
||||
|
@ -5167,19 +5177,22 @@ BEGIN_CASE(JSOP_LEAVEBLOCK)
|
|||
goto error;
|
||||
}
|
||||
|
||||
/* Pop the block chain, too. */
|
||||
regs.fp()->setBlockChain(regs.fp()->blockChain().staticBlockScopeChain());
|
||||
|
||||
/* Move the result of the expression to the new topmost stack slot. */
|
||||
Value *vp = NULL; /* silence GCC warnings */
|
||||
if (op == JSOP_LEAVEBLOCKEXPR)
|
||||
vp = ®s.sp[-1];
|
||||
if (op == JSOP_LEAVEBLOCK) {
|
||||
/* Pop the block's slots. */
|
||||
regs.sp -= GET_UINT16(regs.pc);
|
||||
JS_ASSERT(regs.fp()->base() + blockDepth == regs.sp);
|
||||
} else if (op == JSOP_LEAVEBLOCKEXPR) {
|
||||
/* Pop the block's slots maintaining the topmost expr. */
|
||||
Value *vp = ®s.sp[-1];
|
||||
regs.sp -= GET_UINT16(regs.pc);
|
||||
if (op == JSOP_LEAVEBLOCKEXPR) {
|
||||
JS_ASSERT(regs.fp()->base() + blockDepth == regs.sp - 1);
|
||||
regs.sp[-1] = *vp;
|
||||
} else {
|
||||
JS_ASSERT(regs.fp()->base() + blockDepth == regs.sp);
|
||||
/* Another op will pop; nothing to do here. */
|
||||
len = JSOP_LEAVEFORLETIN_LENGTH;
|
||||
DO_NEXT_OP(len);
|
||||
}
|
||||
}
|
||||
END_CASE(JSOP_LEAVEBLOCK)
|
||||
|
|
|
@ -3678,19 +3678,27 @@ block_setProperty(JSContext *cx, JSObject *obj, jsid id, JSBool strict, Value *v
|
|||
}
|
||||
|
||||
const Shape *
|
||||
JSObject::defineBlockVariable(JSContext *cx, jsid id, intN index)
|
||||
JSObject::defineBlockVariable(JSContext *cx, jsid id, intN index, bool *redeclared)
|
||||
{
|
||||
JS_ASSERT(isStaticBlock());
|
||||
|
||||
*redeclared = false;
|
||||
|
||||
/* Inline JSObject::addProperty in order to trap the redefinition case. */
|
||||
Shape **spp = nativeSearch(cx, id, true);
|
||||
if (SHAPE_FETCH(spp)) {
|
||||
*redeclared = true;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Use JSPROP_ENUMERATE to aid the disassembler, and don't convert this
|
||||
* object to dictionary mode so that we can clone the block's shape later.
|
||||
* Don't convert this object to dictionary mode so that we can clone the
|
||||
* block's shape later.
|
||||
*/
|
||||
uint32_t slot = JSSLOT_FREE(&BlockClass) + index;
|
||||
const Shape *shape = addProperty(cx, id,
|
||||
block_getProperty, block_setProperty,
|
||||
const Shape *shape = addPropertyInternal(cx, id, block_getProperty, block_setProperty,
|
||||
slot, JSPROP_ENUMERATE | JSPROP_PERMANENT,
|
||||
Shape::HAS_SHORTID, index,
|
||||
Shape::HAS_SHORTID, index, spp,
|
||||
/* allowDictionary = */ false);
|
||||
if (!shape)
|
||||
return NULL;
|
||||
|
@ -4191,9 +4199,12 @@ js_XDRBlockObject(JSXDRState *xdr, JSObject **objp)
|
|||
if (!js_XDRAtom(xdr, &atom))
|
||||
return false;
|
||||
|
||||
if (!obj->defineBlockVariable(cx, ATOM_TO_JSID(atom), i))
|
||||
bool redeclared;
|
||||
if (!obj->defineBlockVariable(cx, ATOM_TO_JSID(atom), i, &redeclared)) {
|
||||
JS_ASSERT(!redeclared);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
AutoShapeVector shapes(cx);
|
||||
shapes.growBy(count);
|
||||
|
|
|
@ -1341,7 +1341,7 @@ struct JSObject : js::gc::Cell
|
|||
|
||||
bool swap(JSContext *cx, JSObject *other);
|
||||
|
||||
const js::Shape *defineBlockVariable(JSContext *cx, jsid id, intN index);
|
||||
const js::Shape *defineBlockVariable(JSContext *cx, jsid id, intN index, bool *redeclared);
|
||||
|
||||
inline bool isArguments() const;
|
||||
inline bool isArrayBuffer() const;
|
||||
|
|
|
@ -157,8 +157,7 @@ GetJumpOffset(jsbytecode *pc, jsbytecode *pc2)
|
|||
}
|
||||
|
||||
uintN
|
||||
js_GetIndexFromBytecode(JSContext *cx, JSScript *script, jsbytecode *pc,
|
||||
ptrdiff_t pcoff)
|
||||
js_GetIndexFromBytecode(JSScript *script, jsbytecode *pc, ptrdiff_t pcoff)
|
||||
{
|
||||
JSOp op = JSOp(*pc);
|
||||
JS_ASSERT(js_CodeSpec[op].length >= 1 + pcoff + UINT16_LEN);
|
||||
|
@ -220,10 +219,26 @@ js_GetVariableBytecodeLength(jsbytecode *pc)
|
|||
}
|
||||
}
|
||||
|
||||
uintN
|
||||
js_GetVariableStackUses(JSOp op, jsbytecode *pc)
|
||||
static uint32_t
|
||||
NumBlockSlots(JSScript *script, jsbytecode *pc)
|
||||
{
|
||||
JS_ASSERT(*pc == op);
|
||||
JS_ASSERT(*pc == JSOP_ENTERBLOCK || *pc == JSOP_ENTERLET0 || *pc == JSOP_ENTERLET1);
|
||||
JS_STATIC_ASSERT(JSOP_ENTERBLOCK_LENGTH == JSOP_ENTERLET0_LENGTH);
|
||||
JS_STATIC_ASSERT(JSOP_ENTERBLOCK_LENGTH == JSOP_ENTERLET1_LENGTH);
|
||||
|
||||
JSObject *obj = NULL;
|
||||
GET_OBJECT_FROM_BYTECODE(script, pc, 0, obj);
|
||||
return OBJ_BLOCK_COUNT(NULL, obj);
|
||||
}
|
||||
|
||||
uintN
|
||||
js::StackUses(JSScript *script, jsbytecode *pc)
|
||||
{
|
||||
JSOp op = (JSOp) *pc;
|
||||
const JSCodeSpec &cs = js_CodeSpec[op];
|
||||
if (cs.nuses >= 0)
|
||||
return cs.nuses;
|
||||
|
||||
JS_ASSERT(js_CodeSpec[op].nuses == -1);
|
||||
switch (op) {
|
||||
case JSOP_POPN:
|
||||
|
@ -232,6 +247,10 @@ js_GetVariableStackUses(JSOp op, jsbytecode *pc)
|
|||
return GET_UINT16(pc);
|
||||
case JSOP_LEAVEBLOCKEXPR:
|
||||
return GET_UINT16(pc) + 1;
|
||||
case JSOP_ENTERLET0:
|
||||
return NumBlockSlots(script, pc);
|
||||
case JSOP_ENTERLET1:
|
||||
return NumBlockSlots(script, pc) + 1;
|
||||
default:
|
||||
/* stack: fun, this, [argc arguments] */
|
||||
JS_ASSERT(op == JSOP_NEW || op == JSOP_CALL || op == JSOP_EVAL ||
|
||||
|
@ -241,13 +260,15 @@ js_GetVariableStackUses(JSOp op, jsbytecode *pc)
|
|||
}
|
||||
|
||||
uintN
|
||||
js_GetEnterBlockStackDefs(JSContext *cx, JSScript *script, jsbytecode *pc)
|
||||
js::StackDefs(JSScript *script, jsbytecode *pc)
|
||||
{
|
||||
JSObject *obj;
|
||||
JSOp op = (JSOp) *pc;
|
||||
const JSCodeSpec &cs = js_CodeSpec[op];
|
||||
if (cs.ndefs >= 0)
|
||||
return cs.ndefs;
|
||||
|
||||
JS_ASSERT(*pc == JSOP_ENTERBLOCK);
|
||||
GET_OBJECT_FROM_BYTECODE(script, pc, 0, obj);
|
||||
return OBJ_BLOCK_COUNT(cx, obj);
|
||||
uint32_t n = NumBlockSlots(script, pc);
|
||||
return op == JSOP_ENTERLET1 ? n + 1 : n;
|
||||
}
|
||||
|
||||
static const char * countBaseNames[] = {
|
||||
|
@ -483,8 +504,12 @@ ToDisassemblySource(JSContext *cx, jsval v, JSAutoByteString *bytes)
|
|||
Shape::Range r = obj->lastProperty()->all();
|
||||
while (!r.empty()) {
|
||||
const Shape &shape = r.front();
|
||||
JSAtom *atom = JSID_IS_INT(shape.propid())
|
||||
? cx->runtime->atomState.emptyAtom
|
||||
: JSID_TO_ATOM(shape.propid());
|
||||
|
||||
JSAutoByteString bytes;
|
||||
if (!js_AtomToPrintableString(cx, JSID_TO_ATOM(shape.propid()), &bytes))
|
||||
if (!js_AtomToPrintableString(cx, atom, &bytes))
|
||||
return false;
|
||||
|
||||
r.popFront();
|
||||
|
@ -572,7 +597,7 @@ js_Disassemble1(JSContext *cx, JSScript *script, jsbytecode *pc,
|
|||
case JOF_ATOM:
|
||||
case JOF_OBJECT:
|
||||
case JOF_REGEXP: {
|
||||
uintN index = js_GetIndexFromBytecode(cx, script, pc, 0);
|
||||
uintN index = js_GetIndexFromBytecode(script, pc, 0);
|
||||
jsval v;
|
||||
if (type == JOF_ATOM) {
|
||||
if (op == JSOP_DOUBLE) {
|
||||
|
@ -668,7 +693,7 @@ js_Disassemble1(JSContext *cx, JSScript *script, jsbytecode *pc,
|
|||
case JOF_SLOTATOM:
|
||||
case JOF_SLOTOBJECT: {
|
||||
Sprint(sp, " %u", GET_SLOTNO(pc));
|
||||
uintN index = js_GetIndexFromBytecode(cx, script, pc, SLOTNO_LEN);
|
||||
uintN index = js_GetIndexFromBytecode(script, pc, SLOTNO_LEN);
|
||||
jsval v;
|
||||
if (type == JOF_SLOTATOM) {
|
||||
JSAtom *atom = script->getAtom(index);
|
||||
|
@ -1281,7 +1306,6 @@ GetOff(SprintStack *ss, uintN i)
|
|||
if (off >= 0)
|
||||
return off;
|
||||
|
||||
JS_ASSERT(off <= -2);
|
||||
JS_ASSERT(ss->printer->pcstack);
|
||||
if (off <= -2 && ss->printer->pcstack) {
|
||||
pc = ss->printer->pcstack[-2 - off];
|
||||
|
@ -1361,6 +1385,15 @@ PushOff(SprintStack *ss, ptrdiff_t off, JSOp op, jsbytecode *pc = NULL)
|
|||
return JS_TRUE;
|
||||
}
|
||||
|
||||
static bool
|
||||
PushStr(SprintStack *ss, const char *str, JSOp op)
|
||||
{
|
||||
ptrdiff_t off = SprintCString(&ss->sprinter, str);
|
||||
if (off < 0)
|
||||
return false;
|
||||
return PushOff(ss, off, op);
|
||||
}
|
||||
|
||||
static ptrdiff_t
|
||||
PopOffPrec(SprintStack *ss, uint8_t prec, jsbytecode **ppc = NULL)
|
||||
{
|
||||
|
@ -1673,7 +1706,9 @@ GetLocalInSlot(SprintStack *ss, jsint i, jsint slot, JSObject *obj)
|
|||
const Shape &shape = r.front();
|
||||
|
||||
if (shape.shortid() == slot) {
|
||||
LOCAL_ASSERT(JSID_IS_ATOM(shape.propid()));
|
||||
/* Ignore the empty destructuring dummy. */
|
||||
if (!JSID_IS_ATOM(shape.propid()))
|
||||
continue;
|
||||
|
||||
JSAtom *atom = JSID_TO_ATOM(shape.propid());
|
||||
const char *rval = QuoteString(&ss->sprinter, atom, 0);
|
||||
|
@ -1719,8 +1754,7 @@ GetLocal(SprintStack *ss, jsint i)
|
|||
JS_ASSERT(pc < (ss->printer->script->code + ss->printer->script->length));
|
||||
|
||||
if (JSOP_ENTERBLOCK == (JSOp)*pc) {
|
||||
jsatomid j = js_GetIndexFromBytecode(ss->sprinter.context,
|
||||
ss->printer->script, pc, 0);
|
||||
jsatomid j = js_GetIndexFromBytecode(ss->printer->script, pc, 0);
|
||||
JSObject *obj = script->getObject(j);
|
||||
|
||||
if (obj->isBlock()) {
|
||||
|
@ -1773,17 +1807,32 @@ IsVarSlot(JSPrinter *jp, jsbytecode *pc, jsint *indexp)
|
|||
#define LOAD_ATOM(PCOFF) \
|
||||
GET_ATOM_FROM_BYTECODE(jp->script, pc, PCOFF, atom)
|
||||
|
||||
typedef Vector<JSAtom *, 8> AtomVector;
|
||||
typedef AtomVector::Range AtomRange;
|
||||
|
||||
#if JS_HAS_DESTRUCTURING
|
||||
|
||||
#define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, NULL)
|
||||
#define LOAD_OP_DATA(pc) (oplen = (cs = &js_CodeSpec[op=(JSOp)*pc])->length)
|
||||
|
||||
static jsbytecode *
|
||||
DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc);
|
||||
DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc,
|
||||
AtomRange *letNames = NULL);
|
||||
|
||||
/*
|
||||
* Decompile a single element of a compound {}/[] destructuring lhs, sprinting
|
||||
* the result in-place (without pushing/popping the stack) and advancing the pc
|
||||
* to either the next element or the final pop.
|
||||
*
|
||||
* For normal (SRC_DESTRUCT) destructuring, the names of assigned/initialized
|
||||
* variables are read from their slots. However, for SRC_DESTRUCTLET, the slots
|
||||
* have not been pushed yet; the caller must pass the names to use via
|
||||
* 'letNames'. Each variable initialized in this destructuring lhs results in
|
||||
* popping a name from 'letNames'.
|
||||
*/
|
||||
static jsbytecode *
|
||||
DecompileDestructuringLHS(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc,
|
||||
JSBool *hole)
|
||||
DecompileDestructuringLHS(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc, JSBool *hole,
|
||||
AtomRange *letNames = NULL)
|
||||
{
|
||||
JSPrinter *jp;
|
||||
JSOp op;
|
||||
|
@ -1804,24 +1853,74 @@ DecompileDestructuringLHS(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc,
|
|||
return NULL;
|
||||
break;
|
||||
|
||||
case JSOP_PICK:
|
||||
/*
|
||||
* For 'let ([x, y] = y)', the emitter generates
|
||||
*
|
||||
* push evaluation of y
|
||||
* dup
|
||||
* 1 one
|
||||
* 2 getelem
|
||||
* 3 pick
|
||||
* 4 two
|
||||
* getelem
|
||||
* pick
|
||||
* pop
|
||||
*
|
||||
* Thus 'x' consists of 1 - 3. The caller (DecompileDestructuring or
|
||||
* DecompileGroupAssignment) will have taken care of 1 - 2, so pc is
|
||||
* now pointing at 3. The pick indicates a primitive let var init so
|
||||
* pop a name and advance the pc to 4.
|
||||
*/
|
||||
LOCAL_ASSERT(letNames && !letNames->empty());
|
||||
if (!QuoteString(&ss->sprinter, letNames->popCopyFront(), 0))
|
||||
return NULL;
|
||||
break;
|
||||
|
||||
case JSOP_DUP:
|
||||
pc = DecompileDestructuring(ss, pc, endpc);
|
||||
{
|
||||
/* Compound lhs, e.g., '[x,y]' in 'let [[x,y], z] = a;'. */
|
||||
pc = DecompileDestructuring(ss, pc, endpc, letNames);
|
||||
if (!pc)
|
||||
return NULL;
|
||||
if (pc == endpc)
|
||||
return pc;
|
||||
LOAD_OP_DATA(pc);
|
||||
|
||||
/*
|
||||
* By its post-condition, DecompileDestructuring pushed one string
|
||||
* containing the whole decompiled lhs. Our post-condition is to sprint
|
||||
* in-place so pop/concat this pushed string.
|
||||
*/
|
||||
lval = PopStr(ss, JSOP_NOP);
|
||||
if (SprintCString(&ss->sprinter, lval) < 0)
|
||||
return NULL;
|
||||
|
||||
LOCAL_ASSERT(*pc == JSOP_POP);
|
||||
|
||||
/*
|
||||
* To put block slots in the right place, the emitter follows a
|
||||
* compound lhs with a pick (if at least one slot was pushed). The pick
|
||||
* is not part of the compound lhs so DecompileDestructuring did not
|
||||
* advance over it but it is part of the lhs so advance over it here.
|
||||
*/
|
||||
jsbytecode *nextpc = pc + JSOP_POP_LENGTH;
|
||||
LOCAL_ASSERT(nextpc <= endpc);
|
||||
if (letNames && *nextpc == JSOP_PICK) {
|
||||
LOCAL_ASSERT(nextpc < endpc);
|
||||
pc = nextpc;
|
||||
LOAD_OP_DATA(pc);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JSOP_SETARG:
|
||||
case JSOP_SETLOCAL:
|
||||
LOCAL_ASSERT(!letNames);
|
||||
LOCAL_ASSERT(pc[oplen] == JSOP_POP || pc[oplen] == JSOP_POPN);
|
||||
/* FALL THROUGH */
|
||||
case JSOP_SETLOCALPOP:
|
||||
LOCAL_ASSERT(!letNames);
|
||||
if (op == JSOP_SETARG) {
|
||||
atom = GetArgOrVarAtom(jp, GET_SLOTNO(pc));
|
||||
LOCAL_ASSERT(atom);
|
||||
|
@ -1849,6 +1948,7 @@ DecompileDestructuringLHS(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc,
|
|||
break;
|
||||
|
||||
default: {
|
||||
LOCAL_ASSERT(!letNames);
|
||||
/*
|
||||
* We may need to auto-parenthesize the left-most value decompiled
|
||||
* here, so add back PAREN_SLOP temporarily. Then decompile until the
|
||||
|
@ -1893,59 +1993,54 @@ DecompileDestructuringLHS(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc,
|
|||
}
|
||||
|
||||
/*
|
||||
* Starting with a SRC_DESTRUCT-annotated JSOP_DUP, decompile a destructuring
|
||||
* left-hand side object or array initialiser, including nested destructuring
|
||||
* initialisers. On successful return, the decompilation will be pushed on ss
|
||||
* and the return value will point to the POP or GROUP bytecode following the
|
||||
* destructuring expression.
|
||||
* Decompile a destructuring lhs object or array initialiser, including nested
|
||||
* destructuring initialisers. On return a single string is pushed containing
|
||||
* the entire lhs (regardless of how many variables were bound). Thus, the
|
||||
* caller must take care of fixing up the decompiler stack.
|
||||
*
|
||||
* At any point, if pc is equal to endpc and would otherwise advance, we stop
|
||||
* immediately and return endpc.
|
||||
* See DecompileDestructuringLHS for description of 'letNames'.
|
||||
*/
|
||||
static jsbytecode *
|
||||
DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc)
|
||||
DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc,
|
||||
AtomRange *letNames)
|
||||
{
|
||||
ptrdiff_t head;
|
||||
JSContext *cx;
|
||||
JSPrinter *jp;
|
||||
JSOp op;
|
||||
const JSCodeSpec *cs;
|
||||
uintN oplen;
|
||||
jsint i, lasti;
|
||||
jsdouble d;
|
||||
const char *lval;
|
||||
JSAtom *atom;
|
||||
jssrcnote *sn;
|
||||
JSBool hole;
|
||||
|
||||
LOCAL_ASSERT(*pc == JSOP_DUP);
|
||||
pc += JSOP_DUP_LENGTH;
|
||||
|
||||
JSContext *cx = ss->sprinter.context;
|
||||
JSPrinter *jp = ss->printer;
|
||||
jsbytecode *startpc = pc;
|
||||
|
||||
/*
|
||||
* Set head so we can rewrite '[' to '{' as needed. Back up PAREN_SLOP
|
||||
* chars so the destructuring decompilation accumulates contiguously in
|
||||
* ss->sprinter starting with "[".
|
||||
*/
|
||||
head = SprintPut(&ss->sprinter, "[", 1);
|
||||
ptrdiff_t head = SprintPut(&ss->sprinter, "[", 1);
|
||||
if (head < 0 || !PushOff(ss, head, JSOP_NOP))
|
||||
return NULL;
|
||||
ss->sprinter.offset -= PAREN_SLOP;
|
||||
LOCAL_ASSERT(head == ss->sprinter.offset - 1);
|
||||
LOCAL_ASSERT(*OFF2STR(&ss->sprinter, head) == '[');
|
||||
|
||||
cx = ss->sprinter.context;
|
||||
jp = ss->printer;
|
||||
lasti = -1;
|
||||
int lasti = -1;
|
||||
|
||||
while (pc < endpc) {
|
||||
#if JS_HAS_DESTRUCTURING_SHORTHAND
|
||||
ptrdiff_t nameoff = -1;
|
||||
#endif
|
||||
|
||||
const JSCodeSpec *cs;
|
||||
uintN oplen;
|
||||
JSOp op;
|
||||
LOAD_OP_DATA(pc);
|
||||
|
||||
int i;
|
||||
double d;
|
||||
switch (op) {
|
||||
case JSOP_POP:
|
||||
/* Empty destructuring lhs. */
|
||||
LOCAL_ASSERT(startpc == pc);
|
||||
pc += oplen;
|
||||
goto out;
|
||||
|
||||
|
@ -1963,7 +2058,8 @@ DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc)
|
|||
i = (jsint)d;
|
||||
|
||||
do_getelem:
|
||||
sn = js_GetSrcNote(jp->script, pc);
|
||||
{
|
||||
jssrcnote *sn = js_GetSrcNote(jp->script, pc);
|
||||
pc += oplen;
|
||||
if (pc == endpc)
|
||||
return pc;
|
||||
|
@ -1986,10 +2082,12 @@ DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc)
|
|||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JSOP_GETPROP:
|
||||
case JSOP_LENGTH:
|
||||
{
|
||||
JSAtom *atom;
|
||||
LOAD_ATOM(0);
|
||||
*OFF2STR(&ss->sprinter, head) = '{';
|
||||
#if JS_HAS_DESTRUCTURING_SHORTHAND
|
||||
|
@ -2015,7 +2113,8 @@ DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc)
|
|||
* and continues for a bounded number of bytecodes or stack operations
|
||||
* (and which in any event stops before endpc).
|
||||
*/
|
||||
pc = DecompileDestructuringLHS(ss, pc, endpc, &hole);
|
||||
JSBool hole;
|
||||
pc = DecompileDestructuringLHS(ss, pc, endpc, &hole, letNames);
|
||||
if (!pc)
|
||||
return NULL;
|
||||
|
||||
|
@ -2060,11 +2159,11 @@ DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc)
|
|||
* means another destructuring initialiser abuts this one like in
|
||||
* '[a] = [b] = c'.
|
||||
*/
|
||||
sn = js_GetSrcNote(jp->script, pc);
|
||||
jssrcnote *sn = js_GetSrcNote(jp->script, pc);
|
||||
if (!sn)
|
||||
break;
|
||||
if (SN_TYPE(sn) != SRC_CONTINUE) {
|
||||
LOCAL_ASSERT(SN_TYPE(sn) == SRC_DESTRUCT);
|
||||
LOCAL_ASSERT(SN_TYPE(sn) == SRC_DESTRUCT || SN_TYPE(sn) == SRC_DESTRUCTLET);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -2075,7 +2174,7 @@ DecompileDestructuring(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc)
|
|||
}
|
||||
|
||||
out:
|
||||
lval = OFF2STR(&ss->sprinter, head);
|
||||
const char *lval = OFF2STR(&ss->sprinter, head);
|
||||
if (SprintPut(&ss->sprinter, (*lval == '[') ? "]" : "}", 1) < 0)
|
||||
return NULL;
|
||||
return pc;
|
||||
|
@ -2146,8 +2245,6 @@ DecompileGroupAssignment(SprintStack *ss, jsbytecode *pc, jsbytecode *endpc,
|
|||
|
||||
#define LOCAL_ASSERT(expr) LOCAL_ASSERT_RV(expr, false)
|
||||
|
||||
typedef Vector<JSAtom *, 8> AtomVector;
|
||||
|
||||
/*
|
||||
* The names of the vars of a let block/expr are stored as the ids of the
|
||||
* shapes of the block object. Shapes are stored in a singly-linked list in
|
||||
|
@ -2168,7 +2265,9 @@ GetBlockNames(JSContext *cx, JSObject *blockObj, AtomVector *atoms)
|
|||
LOCAL_ASSERT(shape.hasShortID());
|
||||
--i;
|
||||
LOCAL_ASSERT((uintN)shape.shortid() == i);
|
||||
(*atoms)[i] = JSID_TO_ATOM(shape.propid());
|
||||
(*atoms)[i] = JSID_IS_INT(shape.propid())
|
||||
? cx->runtime->atomState.emptyAtom
|
||||
: JSID_TO_ATOM(shape.propid());
|
||||
}
|
||||
|
||||
LOCAL_ASSERT(i == 0);
|
||||
|
@ -2186,8 +2285,27 @@ PushBlockNames(JSContext *cx, SprintStack *ss, const AtomVector &atoms)
|
|||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* In the scope of a let, the variables' (decompiler) stack slots must contain
|
||||
* the corresponding variable's name. This function updates the N top slots
|
||||
* with the N variable names stored in 'atoms'.
|
||||
*/
|
||||
static bool
|
||||
AssignBlockNamesToPushedSlots(JSContext *cx, SprintStack *ss, const AtomVector &atoms)
|
||||
{
|
||||
/* For simplicity, just pop and push. */
|
||||
LOCAL_ASSERT(atoms.length() <= (uintN)ss->top);
|
||||
for (size_t i = 0; i < atoms.length(); ++i)
|
||||
PopStr(ss, JSOP_NOP);
|
||||
return PushBlockNames(cx, ss, atoms);
|
||||
}
|
||||
|
||||
static const char SkipString[] = "/*skip*/";
|
||||
static const char DestructuredString[] = "/*destructured*/";
|
||||
static const unsigned DestructuredStringLength = ArrayLength(DestructuredString) - 1;
|
||||
|
||||
static ptrdiff_t
|
||||
SprintLet(JSContext *cx, JSPrinter *jp, SprintStack *ss, jsbytecode *pc, ptrdiff_t bodyLength,
|
||||
SprintLetBody(JSContext *cx, JSPrinter *jp, SprintStack *ss, jsbytecode *pc, ptrdiff_t bodyLength,
|
||||
const char *headChars)
|
||||
{
|
||||
if (pc[bodyLength] == JSOP_LEAVEBLOCK) {
|
||||
|
@ -2245,16 +2363,15 @@ GetTokenForAssignment(JSPrinter *jp, jssrcnote *sn, JSOp lastop,
|
|||
}
|
||||
|
||||
static ptrdiff_t
|
||||
SprintNormalFor(JSContext *cx, JSPrinter *jp, SprintStack *ss,
|
||||
const char *init, jsbytecode *initpc,
|
||||
jsbytecode **ppc, ptrdiff_t *plen)
|
||||
SprintNormalFor(JSContext *cx, JSPrinter *jp, SprintStack *ss, const char *initPrefix,
|
||||
const char *init, jsbytecode *initpc, jsbytecode **ppc, ptrdiff_t *plen)
|
||||
{
|
||||
jsbytecode *pc = *ppc;
|
||||
jssrcnote *sn = js_GetSrcNote(jp->script, pc);
|
||||
JS_ASSERT(SN_TYPE(sn) == SRC_FOR);
|
||||
|
||||
/* Print the keyword and the possibly empty init-part. */
|
||||
js_printf(jp, "\tfor (");
|
||||
js_printf(jp, "\tfor (%s", initPrefix);
|
||||
SprintOpcodePermanent(jp, init, initpc);
|
||||
js_printf(jp, ";");
|
||||
|
||||
|
@ -2522,7 +2639,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
|
|||
}
|
||||
saveop = op;
|
||||
len = oplen = cs->length;
|
||||
nuses = js_GetStackUses(cs, op, pc);
|
||||
nuses = StackUses(jp->script, pc);
|
||||
|
||||
/*
|
||||
* Here it is possible that nuses > ss->top when the op has a hidden
|
||||
|
@ -2531,7 +2648,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
|
|||
*/
|
||||
if (nb < 0) {
|
||||
LOCAL_ASSERT(ss->top >= nuses);
|
||||
uintN ndefs = js_GetStackDefs(cx, cs, op, jp->script, pc);
|
||||
uintN ndefs = StackDefs(jp->script, pc);
|
||||
if ((uintN) -(nb + 1) == ss->top - nuses + ndefs)
|
||||
return pc;
|
||||
}
|
||||
|
@ -2671,6 +2788,12 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
|
|||
break;
|
||||
|
||||
case 0:
|
||||
sn = js_GetSrcNote(jp->script, pc);
|
||||
if (sn && SN_TYPE(sn) == SRC_CONTINUE) {
|
||||
/* Hoisted let decl (e.g. 'y' in 'let (x) { let y; }'). */
|
||||
todo = SprintCString(&ss->sprinter, SkipString);
|
||||
break;
|
||||
}
|
||||
todo = SprintCString(&ss->sprinter, token);
|
||||
break;
|
||||
|
||||
|
@ -2712,7 +2835,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
|
|||
|
||||
case SRC_FOR:
|
||||
/* for loop with empty initializer. */
|
||||
todo = SprintNormalFor(cx, jp, ss, "", NULL, &pc, &len);
|
||||
todo = SprintNormalFor(cx, jp, ss, "", "", NULL, &pc, &len);
|
||||
break;
|
||||
|
||||
case SRC_ENDBRACE:
|
||||
|
@ -2875,8 +2998,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
|
|||
} else {
|
||||
/*
|
||||
* Kill newtop before the end_groupassignment: label by
|
||||
* retracting/popping early. Control will either jump
|
||||
* to do_letheadbody: or else break from our case.
|
||||
* retracting/popping early.
|
||||
*/
|
||||
LOCAL_ASSERT(newtop < oldtop);
|
||||
ss->sprinter.offset = GetOff(ss, newtop);
|
||||
|
@ -2908,31 +3030,9 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
|
|||
if (SN_TYPE(sn) == SRC_FOR) {
|
||||
op = JSOP_NOP;
|
||||
pc = pc2;
|
||||
todo = SprintNormalFor(cx, jp, ss, rval, rvalpc, &pc, &len);
|
||||
todo = SprintNormalFor(cx, jp, ss, "", rval, rvalpc, &pc, &len);
|
||||
break;
|
||||
}
|
||||
|
||||
if (SN_TYPE(sn) == SRC_DECL) {
|
||||
if (ss->top == StackDepth(jp->script)) {
|
||||
/*
|
||||
* This must be an empty destructuring
|
||||
* in the head of a let whose body block
|
||||
* is also empty.
|
||||
*/
|
||||
pc = pc2 + JSOP_NOP_LENGTH;
|
||||
len = js_GetSrcNoteOffset(sn, 0);
|
||||
LOCAL_ASSERT(pc[len] == JSOP_LEAVEBLOCK);
|
||||
js_printf(jp, "\tlet (%s) {\n", rval);
|
||||
js_printf(jp, "\t}\n");
|
||||
break;
|
||||
}
|
||||
todo = SprintCString(&ss->sprinter, rval);
|
||||
if (todo < 0 || !PushOff(ss, todo, JSOP_NOP))
|
||||
return NULL;
|
||||
op = JSOP_POP;
|
||||
pc = pc2 + JSOP_NOP_LENGTH;
|
||||
goto do_letheadbody;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* An unnannotated NOP following a POPN must be the
|
||||
|
@ -2988,7 +3088,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
|
|||
if (ss->opcodes[ss->top-1] == JSOP_IN)
|
||||
op = JSOP_LSH;
|
||||
rval = PopStr(ss, op, &rvalpc);
|
||||
todo = SprintNormalFor(cx, jp, ss, rval, rvalpc, &pc, &len);
|
||||
todo = SprintNormalFor(cx, jp, ss, "", rval, rvalpc, &pc, &len);
|
||||
break;
|
||||
|
||||
case SRC_PCDELTA:
|
||||
|
@ -3023,21 +3123,10 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
|
|||
todo = -2;
|
||||
break;
|
||||
|
||||
case SRC_DECL:
|
||||
{
|
||||
/* This pop is at the end of the let block/expr head. */
|
||||
pc += JSOP_POP_LENGTH;
|
||||
#if JS_HAS_DESTRUCTURING
|
||||
do_letheadbody:
|
||||
#endif
|
||||
DupBuffer head(cx);
|
||||
if (!Dup(POP_STR(), &head))
|
||||
return NULL;
|
||||
|
||||
len = js_GetSrcNoteOffset(sn, 0);
|
||||
saveop = (JSOp) pc[len];
|
||||
todo = SprintLet(cx, jp, ss, pc, len, head.begin());
|
||||
}
|
||||
case SRC_CONTINUE:
|
||||
/* Pop the stack, don't print: end of a for-let-in. */
|
||||
(void) PopOff(ss, op);
|
||||
todo = -2;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -3236,6 +3325,157 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
|
|||
break;
|
||||
}
|
||||
|
||||
case JSOP_ENTERLET0:
|
||||
{
|
||||
LOAD_OBJECT(0);
|
||||
|
||||
AtomVector atoms(cx);
|
||||
if (!GetBlockNames(cx, obj, &atoms))
|
||||
return NULL;
|
||||
|
||||
sn = js_GetSrcNote(jp->script, pc);
|
||||
LOCAL_ASSERT(SN_TYPE(sn) == SRC_DECL);
|
||||
ptrdiff_t letData = js_GetSrcNoteOffset(sn, 0);
|
||||
bool groupAssign = LetDataToGroupAssign(letData);
|
||||
uintN letDepth = OBJ_BLOCK_DEPTH(cx, obj);
|
||||
LOCAL_ASSERT(letDepth == (uintN)ss->top - OBJ_BLOCK_COUNT(cx, obj));
|
||||
LOCAL_ASSERT(atoms.length() == OBJ_BLOCK_COUNT(cx, obj));
|
||||
|
||||
/*
|
||||
* Build the list of decompiled rhs expressions. Do this before
|
||||
* sprinting the let-head since GetStr can inject stuff on top
|
||||
* of the stack (in case js_DecompileValueGenerator).
|
||||
*/
|
||||
Vector<const char *> rhsExprs(cx);
|
||||
if (!rhsExprs.resize(atoms.length()))
|
||||
return false;
|
||||
for (size_t i = 0; i < rhsExprs.length(); ++i) {
|
||||
rhsExprs[i] = GetStr(ss, letDepth + i);
|
||||
if (!rhsExprs[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Build the let head starting at headBegin. */
|
||||
ptrdiff_t headBegin = ss->sprinter.offset;
|
||||
|
||||
/*
|
||||
* For group assignment, prepend the '[lhs-vars] = [' here,
|
||||
* append rhsExprs in the next loop and append ']' after.
|
||||
*/
|
||||
if (groupAssign) {
|
||||
if (Sprint(&ss->sprinter, "[") < 0)
|
||||
return false;
|
||||
for (size_t i = 0; i < atoms.length(); ++i) {
|
||||
if (i && Sprint(&ss->sprinter, ", ") < 0)
|
||||
return false;
|
||||
if (!QuoteString(&ss->sprinter, atoms[i], 0))
|
||||
return false;
|
||||
}
|
||||
if (Sprint(&ss->sprinter, "] = [") < 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < atoms.length(); ++i) {
|
||||
const char *rhs = rhsExprs[i];
|
||||
if (!strcmp(rhs, SkipString))
|
||||
continue;
|
||||
|
||||
if (i && Sprint(&ss->sprinter, ", ") < 0)
|
||||
return false;
|
||||
|
||||
if (groupAssign) {
|
||||
if (SprintCString(&ss->sprinter, rhs) < 0)
|
||||
return false;
|
||||
} else if (!strncmp(rhs, DestructuredString, DestructuredStringLength)) {
|
||||
if (SprintCString(&ss->sprinter, rhs + DestructuredStringLength) < 0)
|
||||
return false;
|
||||
} else {
|
||||
JS_ASSERT(atoms[i] != cx->runtime->atomState.emptyAtom);
|
||||
if (!QuoteString(&ss->sprinter, atoms[i], 0))
|
||||
return false;
|
||||
if (*rhs) {
|
||||
uint8_t prec = js_CodeSpec[ss->opcodes[letDepth + i]].prec;
|
||||
const char *fmt = prec && prec < js_CodeSpec[JSOP_SETLOCAL].prec
|
||||
? " = (%s)"
|
||||
: " = %s";
|
||||
if (Sprint(&ss->sprinter, fmt, rhs) < 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (groupAssign && Sprint(&ss->sprinter, "]") < 0)
|
||||
return false;
|
||||
|
||||
/* Clone the let head chars before clobbering the stack. */
|
||||
DupBuffer head(cx);
|
||||
if (!Dup(OFF2STR(&ss->sprinter, headBegin), &head))
|
||||
return NULL;
|
||||
if (!AssignBlockNamesToPushedSlots(cx, ss, atoms))
|
||||
return NULL;
|
||||
|
||||
/* Detect 'for (let ...)' desugared into 'let (...) {for}'. */
|
||||
jsbytecode *nextpc = pc + JSOP_ENTERLET0_LENGTH;
|
||||
if (*nextpc == JSOP_NOP) {
|
||||
jssrcnote *nextsn = js_GetSrcNote(jp->script, nextpc);
|
||||
if (nextsn && SN_TYPE(nextsn) == SRC_FOR) {
|
||||
pc = nextpc;
|
||||
todo = SprintNormalFor(cx, jp, ss, "let ", head.begin(), pc, &pc, &len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Decompile the body and then complete the let block/expr. */
|
||||
len = LetDataToOffset(letData);
|
||||
pc = nextpc;
|
||||
saveop = (JSOp) pc[len];
|
||||
todo = SprintLetBody(cx, jp, ss, pc, len, head.begin());
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* With 'for (let lhs in rhs)' and 'switch (c) { let-decl }',
|
||||
* placeholder slots have already been pushed (by JSOP_UNDEFINED).
|
||||
* In both the for-let-in and switch-hoisted-let cases:
|
||||
* - there is a non-let slot on top of the stack (hence enterlet1)
|
||||
* - there is no further special let-handling required:
|
||||
* for-let-in will decompile the let head when it decompiles
|
||||
* the loop body prologue; there is no let head to decompile
|
||||
* with switch.
|
||||
* Hence, the only thing to do is update the let vars' slots with
|
||||
* their names, taking care to preserve the iter/condition value
|
||||
* on top of the stack.
|
||||
*/
|
||||
case JSOP_ENTERLET1:
|
||||
{
|
||||
LOAD_OBJECT(0);
|
||||
|
||||
AtomVector atoms(cx);
|
||||
if (!GetBlockNames(cx, obj, &atoms))
|
||||
return NULL;
|
||||
|
||||
LOCAL_ASSERT(js_GetSrcNote(jp->script, pc) == NULL);
|
||||
LOCAL_ASSERT(ss->top - 1 == OBJ_BLOCK_DEPTH(cx, obj) + OBJ_BLOCK_COUNT(cx, obj));
|
||||
jsbytecode *nextpc = pc + JSOP_ENTERLET1_LENGTH;
|
||||
if (*nextpc == JSOP_GOTO || *nextpc == JSOP_GOTOX) {
|
||||
LOCAL_ASSERT(SN_TYPE(js_GetSrcNote(jp->script, nextpc)) == SRC_FOR_IN);
|
||||
} else {
|
||||
LOCAL_ASSERT(*nextpc == JSOP_CONDSWITCH ||
|
||||
*nextpc == JSOP_TABLESWITCH || *nextpc == JSOP_TABLESWITCHX ||
|
||||
*nextpc == JSOP_LOOKUPSWITCH || *nextpc == JSOP_LOOKUPSWITCHX);
|
||||
}
|
||||
|
||||
DupBuffer rhs(cx);
|
||||
if (!Dup(PopStr(ss, JSOP_NOP), &rhs))
|
||||
return NULL;
|
||||
if (!AssignBlockNamesToPushedSlots(cx, ss, atoms))
|
||||
return NULL;
|
||||
if (!PushStr(ss, rhs.begin(), op))
|
||||
return NULL;
|
||||
todo = -2;
|
||||
break;
|
||||
}
|
||||
|
||||
case JSOP_GETFCSLOT:
|
||||
case JSOP_CALLFCSLOT:
|
||||
{
|
||||
|
@ -3591,7 +3831,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
|
|||
}
|
||||
|
||||
/*
|
||||
* Do not AddParentSlop here, as we will push the
|
||||
* Do not AddParenSlop here, as we will push the
|
||||
* top-most offset again, which will add paren slop
|
||||
* for us. We must push to balance the stack budget
|
||||
* when nesting for heads in a comprehension.
|
||||
|
@ -3857,26 +4097,107 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb)
|
|||
#if JS_HAS_DESTRUCTURING
|
||||
sn = js_GetSrcNote(jp->script, pc);
|
||||
if (sn) {
|
||||
LOCAL_ASSERT(SN_TYPE(sn) == SRC_DESTRUCT);
|
||||
if (SN_TYPE(sn) == SRC_DESTRUCT) {
|
||||
pc = DecompileDestructuring(ss, pc, endpc);
|
||||
if (!pc)
|
||||
return NULL;
|
||||
len = 0;
|
||||
lval = POP_STR();
|
||||
op = saveop = JSOP_ENUMELEM;
|
||||
rval = POP_STR();
|
||||
|
||||
lval = POP_STR(); /* Pop the decompiler result. */
|
||||
rval = POP_STR(); /* Pop the initializer expression. */
|
||||
|
||||
if (strcmp(rval, forelem_cookie) == 0) {
|
||||
todo = Sprint(&ss->sprinter, ss_format,
|
||||
VarPrefix(sn), lval);
|
||||
|
||||
// Skip POP so the SRC_FOR_IN code can pop for itself.
|
||||
/* Skip POP so the SRC_FOR_IN code can pop for itself. */
|
||||
if (*pc == JSOP_POP)
|
||||
len = JSOP_POP_LENGTH;
|
||||
} else {
|
||||
todo = Sprint(&ss->sprinter, "%s%s = %s",
|
||||
VarPrefix(sn), lval, rval);
|
||||
}
|
||||
|
||||
op = saveop = JSOP_ENUMELEM;
|
||||
len = 0;
|
||||
} else {
|
||||
LOCAL_ASSERT(SN_TYPE(sn) == SRC_DESTRUCTLET);
|
||||
|
||||
ptrdiff_t offsetToLet = js_GetSrcNoteOffset(sn, 0);
|
||||
LOCAL_ASSERT(*(pc + offsetToLet) == JSOP_ENTERLET0);
|
||||
|
||||
GET_OBJECT_FROM_BYTECODE(jp->script, pc + offsetToLet, 0, obj);
|
||||
|
||||
uint32_t blockDepth = OBJ_BLOCK_DEPTH(cx, obj);
|
||||
LOCAL_ASSERT(blockDepth < ss->top);
|
||||
LOCAL_ASSERT(ss->top <= blockDepth + OBJ_BLOCK_COUNT(cx, obj));
|
||||
|
||||
AtomVector atoms(cx);
|
||||
if (!GetBlockNames(cx, obj, &atoms))
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Skip any initializers preceding this one. E.g., in
|
||||
* let (w=1, x=2, [y,z] = a) { ... }
|
||||
* skip 'w' and 'x' for the JSOP_DUP of '[y,z] = a'.
|
||||
*/
|
||||
AtomRange letNames = atoms.all();
|
||||
uint32_t curDepth = ss->top - 1 /* initializer */;
|
||||
for (uint32_t i = blockDepth; i < curDepth; ++i)
|
||||
letNames.popFront();
|
||||
|
||||
/*
|
||||
* Pop and copy the rhs before it gets clobbered.
|
||||
* Use JSOP_SETLOCAL's precedence since this is =.
|
||||
*/
|
||||
DupBuffer rhs(cx);
|
||||
if (!Dup(PopStr(ss, JSOP_SETLOCAL), &rhs))
|
||||
return NULL;
|
||||
|
||||
/* Destructure, tracking how many vars were bound. */
|
||||
size_t remainBefore = letNames.remain();
|
||||
pc = DecompileDestructuring(ss, pc, endpc, &letNames);
|
||||
if (!pc)
|
||||
return NULL;
|
||||
size_t remainAfter = letNames.remain();
|
||||
|
||||
/*
|
||||
* Merge the lhs and rhs and prefix with a cookie to
|
||||
* tell enterlet0 not to prepend "name = ".
|
||||
*/
|
||||
const char *lhs = PopStr(ss, JSOP_NOP);
|
||||
ptrdiff_t off = Sprint(&ss->sprinter, "%s%s = %s",
|
||||
DestructuredString, lhs, rhs.begin());
|
||||
if (off < 0 || !PushOff(ss, off, JSOP_NOP))
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Only one slot has been pushed (holding the entire
|
||||
* decompiled destructuring expression). However, the
|
||||
* abstract depth needs one slot per bound var, so push
|
||||
* empty strings for the remainder. We don't have to
|
||||
* worry about empty destructuring because the parser
|
||||
* ensures that there is always at least one pushed
|
||||
* slot for each destructuring lhs.
|
||||
*/
|
||||
LOCAL_ASSERT(remainBefore >= remainAfter);
|
||||
LOCAL_ASSERT(remainBefore > remainAfter || remainAfter > 0);
|
||||
for (size_t i = remainBefore - 1; i > remainAfter; --i) {
|
||||
if (!PushStr(ss, SkipString, JSOP_NOP))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOCAL_ASSERT(*pc == JSOP_POP);
|
||||
pc += JSOP_POP_LENGTH;
|
||||
|
||||
/* Eat up the JSOP_UNDEFINED following empty destructuring. */
|
||||
if (remainBefore == remainAfter) {
|
||||
LOCAL_ASSERT(*pc == JSOP_UNDEFINED);
|
||||
pc += JSOP_UNDEFINED_LENGTH;
|
||||
}
|
||||
|
||||
len = 0;
|
||||
todo = -2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
@ -5534,8 +5855,8 @@ static intN
|
|||
SimulateOp(JSContext *cx, JSScript *script, JSOp op, const JSCodeSpec *cs,
|
||||
jsbytecode *pc, jsbytecode **pcstack, uintN &pcdepth)
|
||||
{
|
||||
uintN nuses = js_GetStackUses(cs, op, pc);
|
||||
uintN ndefs = js_GetStackDefs(cx, cs, op, script, pc);
|
||||
uintN nuses = StackUses(script, pc);
|
||||
uintN ndefs = StackDefs(script, pc);
|
||||
LOCAL_ASSERT(pcdepth >= nuses);
|
||||
pcdepth -= nuses;
|
||||
LOCAL_ASSERT(pcdepth + ndefs <= StackDepth(script));
|
||||
|
|
|
@ -332,8 +332,7 @@ js_puts(JSPrinter *jp, const char *s);
|
|||
* lexical environments.
|
||||
*/
|
||||
uintN
|
||||
js_GetIndexFromBytecode(JSContext *cx, JSScript *script, jsbytecode *pc,
|
||||
ptrdiff_t pcoff);
|
||||
js_GetIndexFromBytecode(JSScript *script, jsbytecode *pc, ptrdiff_t pcoff);
|
||||
|
||||
/*
|
||||
* A slower version of GET_ATOM when the caller does not want to maintain
|
||||
|
@ -342,72 +341,46 @@ js_GetIndexFromBytecode(JSContext *cx, JSScript *script, jsbytecode *pc,
|
|||
#define GET_ATOM_FROM_BYTECODE(script, pc, pcoff, atom) \
|
||||
JS_BEGIN_MACRO \
|
||||
JS_ASSERT(*(pc) != JSOP_DOUBLE); \
|
||||
uintN index_ = js_GetIndexFromBytecode(cx, (script), (pc), (pcoff)); \
|
||||
uintN index_ = js_GetIndexFromBytecode((script), (pc), (pcoff)); \
|
||||
(atom) = (script)->getAtom(index_); \
|
||||
JS_END_MACRO
|
||||
|
||||
#define GET_DOUBLE_FROM_BYTECODE(script, pc, pcoff, dbl) \
|
||||
JS_BEGIN_MACRO \
|
||||
uintN index_ = js_GetIndexFromBytecode(cx, (script), (pc), (pcoff)); \
|
||||
uintN index_ = js_GetIndexFromBytecode((script), (pc), (pcoff)); \
|
||||
JS_ASSERT(index_ < (script)->consts()->length); \
|
||||
(dbl) = (script)->getConst(index_).toDouble(); \
|
||||
JS_END_MACRO
|
||||
|
||||
#define GET_OBJECT_FROM_BYTECODE(script, pc, pcoff, obj) \
|
||||
JS_BEGIN_MACRO \
|
||||
uintN index_ = js_GetIndexFromBytecode(cx, (script), (pc), (pcoff)); \
|
||||
uintN index_ = js_GetIndexFromBytecode((script), (pc), (pcoff)); \
|
||||
obj = (script)->getObject(index_); \
|
||||
JS_END_MACRO
|
||||
|
||||
#define GET_FUNCTION_FROM_BYTECODE(script, pc, pcoff, fun) \
|
||||
JS_BEGIN_MACRO \
|
||||
uintN index_ = js_GetIndexFromBytecode(cx, (script), (pc), (pcoff)); \
|
||||
uintN index_ = js_GetIndexFromBytecode((script), (pc), (pcoff)); \
|
||||
fun = (script)->getFunction(index_); \
|
||||
JS_END_MACRO
|
||||
|
||||
#define GET_REGEXP_FROM_BYTECODE(script, pc, pcoff, obj) \
|
||||
JS_BEGIN_MACRO \
|
||||
uintN index_ = js_GetIndexFromBytecode(cx, (script), (pc), (pcoff)); \
|
||||
uintN index_ = js_GetIndexFromBytecode((script), (pc), (pcoff)); \
|
||||
obj = (script)->getRegExp(index_); \
|
||||
JS_END_MACRO
|
||||
|
||||
/*
|
||||
* Find the number of stack slots used by a variadic opcode such as JSOP_CALL
|
||||
* (for such ops, JSCodeSpec.nuses is -1).
|
||||
*/
|
||||
#ifdef __cplusplus
|
||||
namespace js {
|
||||
|
||||
extern uintN
|
||||
js_GetVariableStackUses(JSOp op, jsbytecode *pc);
|
||||
StackUses(JSScript *script, jsbytecode *pc);
|
||||
|
||||
/*
|
||||
* Find the number of stack slots defined by JSOP_ENTERBLOCK (for this op,
|
||||
* JSCodeSpec.ndefs is -1).
|
||||
*/
|
||||
extern uintN
|
||||
js_GetEnterBlockStackDefs(JSContext *cx, JSScript *script, jsbytecode *pc);
|
||||
StackDefs(JSScript *script, jsbytecode *pc);
|
||||
|
||||
#ifdef __cplusplus /* Aargh, libgjs, bug 492720. */
|
||||
static JS_INLINE uintN
|
||||
js_GetStackUses(const JSCodeSpec *cs, JSOp op, jsbytecode *pc)
|
||||
{
|
||||
JS_ASSERT(cs == &js_CodeSpec[op]);
|
||||
if (cs->nuses >= 0)
|
||||
return cs->nuses;
|
||||
return js_GetVariableStackUses(op, pc);
|
||||
}
|
||||
|
||||
static JS_INLINE uintN
|
||||
js_GetStackDefs(JSContext *cx, const JSCodeSpec *cs, JSOp op, JSScript *script,
|
||||
jsbytecode *pc)
|
||||
{
|
||||
JS_ASSERT(cs == &js_CodeSpec[op]);
|
||||
if (cs->ndefs >= 0)
|
||||
return cs->ndefs;
|
||||
|
||||
/* Only JSOP_ENTERBLOCK has a variable number of stack defs. */
|
||||
JS_ASSERT(op == JSOP_ENTERBLOCK);
|
||||
return js_GetEnterBlockStackDefs(cx, script, pc);
|
||||
}
|
||||
#endif
|
||||
} /* namespace js */
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* Decompilers, for script, function, and expression pretty-printing.
|
||||
|
|
|
@ -271,7 +271,8 @@ OPDEF(JSOP_DECLOCAL, 102,"declocal", NULL, 3, 0, 1, 15, JOF_LOCAL|
|
|||
OPDEF(JSOP_LOCALINC, 103,"localinc", NULL, 3, 0, 1, 15, JOF_LOCAL|JOF_NAME|JOF_INC|JOF_POST|JOF_TMPSLOT3)
|
||||
OPDEF(JSOP_LOCALDEC, 104,"localdec", NULL, 3, 0, 1, 15, JOF_LOCAL|JOF_NAME|JOF_DEC|JOF_POST|JOF_TMPSLOT3)
|
||||
|
||||
OPDEF(JSOP_UNUSED1, 105,"unused0", NULL, 1, 0, 0, 0, JOF_BYTE)
|
||||
/* Leave a for-let-in block leaving its storage pushed (to be popped after enditer). */
|
||||
OPDEF(JSOP_LEAVEFORLETIN, 105,"leaveforletin",NULL, 1, 0, 0, 0, JOF_BYTE)
|
||||
|
||||
/* The argument is the offset to the next statement and is used by IonMonkey. */
|
||||
OPDEF(JSOP_LABEL, 106,"label", NULL, 3, 0, 0, 0, JOF_JUMP)
|
||||
|
@ -440,8 +441,11 @@ OPDEF(JSOP_DELDESC, 183,"deldesc", NULL, 1, 2, 1, 15, JOF_BYTE|J
|
|||
|
||||
OPDEF(JSOP_CALLPROP, 184,"callprop", NULL, 3, 1, 2, 18, JOF_ATOM|JOF_PROP|JOF_TYPESET|JOF_CALLOP|JOF_TMPSLOT3)
|
||||
|
||||
OPDEF(JSOP_UNUSED2, 185,"unused1", NULL, 1, 0, 0, 0, JOF_BYTE)
|
||||
OPDEF(JSOP_UNUSED3, 186,"unused2", NULL, 1, 0, 0, 0, JOF_BYTE)
|
||||
/* Enter a let block/expr whose slots are at the top of the stack. */
|
||||
OPDEF(JSOP_ENTERLET0, 185,"enterlet0", NULL, 3, -1, -1, 0, JOF_OBJECT)
|
||||
|
||||
/* Enter a let block/expr whose slots are 1 below the top of the stack. */
|
||||
OPDEF(JSOP_ENTERLET1, 186,"enterlet1", NULL, 3, -1, -1, 0, JOF_OBJECT)
|
||||
|
||||
/*
|
||||
* Opcode to hold 24-bit immediate integer operands.
|
||||
|
|
|
@ -1623,7 +1623,7 @@ class ASTSerializer
|
|||
bool declaration(ParseNode *pn, Value *dst);
|
||||
bool variableDeclaration(ParseNode *pn, bool let, Value *dst);
|
||||
bool variableDeclarator(ParseNode *pn, VarDeclKind *pkind, Value *dst);
|
||||
bool letHead(ParseNode *pn, NodeVector &dtors);
|
||||
bool let(ParseNode *pn, bool expr, Value *dst);
|
||||
|
||||
bool optStatement(ParseNode *pn, Value *dst) {
|
||||
if (!pn) {
|
||||
|
@ -1963,14 +1963,21 @@ ASTSerializer::variableDeclarator(ParseNode *pn, VarDeclKind *pkind, Value *dst)
|
|||
}
|
||||
|
||||
bool
|
||||
ASTSerializer::letHead(ParseNode *pn, NodeVector &dtors)
|
||||
ASTSerializer::let(ParseNode *pn, bool expr, Value *dst)
|
||||
{
|
||||
if (!dtors.reserve(pn->pn_count))
|
||||
ParseNode *letHead = pn->pn_left;
|
||||
LOCAL_ASSERT(letHead->isArity(PN_LIST));
|
||||
|
||||
ParseNode *letBody = pn->pn_right;
|
||||
LOCAL_ASSERT(letBody->isKind(PNK_LEXICALSCOPE));
|
||||
|
||||
NodeVector dtors(cx);
|
||||
if (!dtors.reserve(letHead->pn_count))
|
||||
return false;
|
||||
|
||||
VarDeclKind kind = VARDECL_LET_HEAD;
|
||||
|
||||
for (ParseNode *next = pn->pn_head; next; next = next->pn_next) {
|
||||
for (ParseNode *next = letHead->pn_head; next; next = next->pn_next) {
|
||||
Value child;
|
||||
/*
|
||||
* Unlike in |variableDeclaration|, this does not update |kind|; since let-heads do
|
||||
|
@ -1981,7 +1988,12 @@ ASTSerializer::letHead(ParseNode *pn, NodeVector &dtors)
|
|||
dtors.infallibleAppend(child);
|
||||
}
|
||||
|
||||
return true;
|
||||
Value v;
|
||||
return expr
|
||||
? expression(letBody->pn_expr, &v) &&
|
||||
builder.letExpression(dtors, v, &pn->pn_pos, dst)
|
||||
: statement(letBody->pn_expr, &v) &&
|
||||
builder.letStatement(dtors, v, &pn->pn_pos, dst);
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -2078,8 +2090,6 @@ ASTSerializer::forInit(ParseNode *pn, Value *dst)
|
|||
|
||||
return (pn->isKind(PNK_VAR) || pn->isKind(PNK_CONST))
|
||||
? variableDeclaration(pn, false, dst)
|
||||
: pn->isKind(PNK_LET)
|
||||
? variableDeclaration(pn, true, dst)
|
||||
: expression(pn, dst);
|
||||
}
|
||||
|
||||
|
@ -2091,9 +2101,13 @@ ASTSerializer::statement(ParseNode *pn, Value *dst)
|
|||
case PNK_FUNCTION:
|
||||
case PNK_VAR:
|
||||
case PNK_CONST:
|
||||
case PNK_LET:
|
||||
return declaration(pn, dst);
|
||||
|
||||
case PNK_LET:
|
||||
return pn->isArity(PN_BINARY)
|
||||
? let(pn, false, dst)
|
||||
: declaration(pn, dst);
|
||||
|
||||
case PNK_NAME:
|
||||
LOCAL_ASSERT(pn->isUsed());
|
||||
return statement(pn->pn_lexdef, dst);
|
||||
|
@ -2108,15 +2122,6 @@ ASTSerializer::statement(ParseNode *pn, Value *dst)
|
|||
|
||||
case PNK_LEXICALSCOPE:
|
||||
pn = pn->pn_expr;
|
||||
if (pn->isKind(PNK_LET)) {
|
||||
NodeVector dtors(cx);
|
||||
Value stmt;
|
||||
|
||||
return letHead(pn->pn_left, dtors) &&
|
||||
statement(pn->pn_right, &stmt) &&
|
||||
builder.letStatement(dtors, stmt, &pn->pn_pos, dst);
|
||||
}
|
||||
|
||||
if (!pn->isKind(PNK_STATEMENTLIST))
|
||||
return statement(pn, dst);
|
||||
/* FALL THROUGH */
|
||||
|
@ -2176,9 +2181,9 @@ ASTSerializer::statement(ParseNode *pn, Value *dst)
|
|||
|
||||
return (!head->pn_kid1
|
||||
? pattern(head->pn_kid2, NULL, &var)
|
||||
: variableDeclaration(head->pn_kid1,
|
||||
head->pn_kid1->isKind(PNK_LET),
|
||||
&var)) &&
|
||||
: head->pn_kid1->isKind(PNK_LEXICALSCOPE)
|
||||
? variableDeclaration(head->pn_kid1->pn_expr, true, &var)
|
||||
: variableDeclaration(head->pn_kid1, false, &var)) &&
|
||||
expression(head->pn_kid3, &expr) &&
|
||||
builder.forInStatement(var, expr, stmt, isForEach, &pn->pn_pos, dst);
|
||||
}
|
||||
|
@ -2633,17 +2638,8 @@ ASTSerializer::expression(ParseNode *pn, Value *dst)
|
|||
|
||||
return comprehension(pn->pn_head->pn_expr, dst);
|
||||
|
||||
case PNK_LEXICALSCOPE:
|
||||
{
|
||||
pn = pn->pn_expr;
|
||||
|
||||
NodeVector dtors(cx);
|
||||
Value expr;
|
||||
|
||||
return letHead(pn->pn_left, dtors) &&
|
||||
expression(pn->pn_right, &expr) &&
|
||||
builder.letExpression(dtors, expr, &pn->pn_pos, dst);
|
||||
}
|
||||
case PNK_LET:
|
||||
return let(pn, true, dst);
|
||||
|
||||
#ifdef JS_HAS_XML_SUPPORT
|
||||
case PNK_XMLUNARY:
|
||||
|
|
|
@ -226,7 +226,7 @@ JS_XDRFindClassById(JSXDRState *xdr, uint32_t id);
|
|||
* and saved versions. If deserialization fails, the data should be
|
||||
* invalidated if possible.
|
||||
*/
|
||||
#define JSXDR_BYTECODE_VERSION (0xb973c0de - 99)
|
||||
#define JSXDR_BYTECODE_VERSION (0xb973c0de - 100)
|
||||
|
||||
/*
|
||||
* Library-private functions.
|
||||
|
|
|
@ -2678,6 +2678,8 @@ mjit::Compiler::generateMethod()
|
|||
END_CASE(JSOP_GETXPROP)
|
||||
|
||||
BEGIN_CASE(JSOP_ENTERBLOCK)
|
||||
BEGIN_CASE(JSOP_ENTERLET0)
|
||||
BEGIN_CASE(JSOP_ENTERLET1)
|
||||
enterBlock(script->getObject(fullAtomIndex(PC)));
|
||||
END_CASE(JSOP_ENTERBLOCK);
|
||||
|
||||
|
@ -7134,9 +7136,9 @@ mjit::Compiler::enterBlock(JSObject *obj)
|
|||
/* For now, don't bother doing anything for this opcode. */
|
||||
frame.syncAndForgetEverything();
|
||||
masm.move(ImmPtr(obj), Registers::ArgReg1);
|
||||
uint32_t n = js_GetEnterBlockStackDefs(cx, script, PC);
|
||||
INLINE_STUBCALL(stubs::EnterBlock, REJOIN_NONE);
|
||||
frame.enterBlock(n);
|
||||
if (*PC == JSOP_ENTERBLOCK)
|
||||
frame.enterBlock(StackDefs(script, PC));
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -7146,7 +7148,7 @@ mjit::Compiler::leaveBlock()
|
|||
* Note: After bug 535912, we can pass the block obj directly, inline
|
||||
* PutBlockObject, and do away with the muckiness in PutBlockObject.
|
||||
*/
|
||||
uint32_t n = js_GetVariableStackUses(JSOP_LEAVEBLOCK, PC);
|
||||
uint32_t n = StackUses(script, PC);
|
||||
prepareStubCall(Uses(n));
|
||||
INLINE_STUBCALL(stubs::LeaveBlock, REJOIN_NONE);
|
||||
frame.leaveBlock(n);
|
||||
|
|
|
@ -1869,7 +1869,7 @@ LoopState::analyzeLoopBody(unsigned frame)
|
|||
|
||||
case JSOP_SETPROP:
|
||||
case JSOP_SETMETHOD: {
|
||||
JSAtom *atom = script->getAtom(js_GetIndexFromBytecode(cx, script, pc, 0));
|
||||
JSAtom *atom = script->getAtom(js_GetIndexFromBytecode(script, pc, 0));
|
||||
jsid id = MakeTypeId(cx, ATOM_TO_JSID(atom));
|
||||
|
||||
TypeSet *objTypes = analysis->poppedTypes(pc, 1);
|
||||
|
@ -2185,7 +2185,7 @@ LoopState::getEntryValue(const CrossSSAValue &iv, uint32_t *pslot, int32_t *pcon
|
|||
}
|
||||
|
||||
case JSOP_GETPROP: {
|
||||
JSAtom *atom = script->getAtom(js_GetIndexFromBytecode(cx, script, pc, 0));
|
||||
JSAtom *atom = script->getAtom(js_GetIndexFromBytecode(script, pc, 0));
|
||||
jsid id = ATOM_TO_JSID(atom);
|
||||
CrossSSAValue objcv(cv.frame, analysis->poppedValue(v.pushedOffset(), 0));
|
||||
FrameEntry *tmp = invariantProperty(objcv, id);
|
||||
|
|
|
@ -1821,12 +1821,15 @@ stubs::EnterBlock(VMFrame &f, JSObject *obj)
|
|||
|
||||
JS_ASSERT(!f.regs.inlined());
|
||||
JS_ASSERT(obj->isStaticBlock());
|
||||
|
||||
if (*regs.pc == JSOP_ENTERBLOCK) {
|
||||
JS_ASSERT(fp->base() + OBJ_BLOCK_DEPTH(cx, obj) == regs.sp);
|
||||
Value *vp = regs.sp + OBJ_BLOCK_COUNT(cx, obj);
|
||||
JS_ASSERT(regs.sp < vp);
|
||||
JS_ASSERT(vp <= fp->slots() + fp->script()->nslots);
|
||||
SetValueRangeToUndefined(regs.sp, vp);
|
||||
regs.sp = vp;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
JSContext *cx = f.cx;
|
||||
|
|
|
@ -49,11 +49,11 @@ expect = '99999';
|
|||
jit(true);
|
||||
|
||||
for (let j = 0; j < 5; ++j) {
|
||||
var e;
|
||||
e = 9;
|
||||
print(actual += '' + e);
|
||||
e = 47;
|
||||
if (e & 0) {
|
||||
var e;
|
||||
let e;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -575,7 +575,7 @@ function testVarPatternCombinations(makePattSrc, makePattPatt) {
|
|||
assertStmt("for (var " + pattSrcs[i].join(",") + "; foo; bar);",
|
||||
forStmt(varDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt));
|
||||
assertStmt("for (let " + pattSrcs[i].join(",") + "; foo; bar);",
|
||||
forStmt(letDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt));
|
||||
letStmt(pattPatts[i], forStmt(null, ident("foo"), ident("bar"), emptyStmt)));
|
||||
assertStmt("for (const " + pattSrcs[i].join(",") + "; foo; bar);",
|
||||
forStmt(constDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt));
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ function f() {
|
|||
let (a = let (x = 1) x) {}
|
||||
}
|
||||
|
||||
trap(f, 3, 'assertEq(evalInFrame(1, "a"), 0)');
|
||||
trap(f, 4, 'assertEq(evalInFrame(1, "a"), 0)');
|
||||
f();
|
||||
|
||||
reportCompare(0, 0, 'ok');
|
||||
|
|
|
@ -5,7 +5,7 @@ var e = [], x = {b: []};
|
|||
function f() {
|
||||
let (a = [[] for (x in e)], {b: []} = x) {}
|
||||
}
|
||||
trap(f, 4, '');
|
||||
trap(f, 3, '');
|
||||
f();
|
||||
|
||||
reportCompare(0, 0, 'ok');
|
||||
|
|
Загрузка…
Ссылка в новой задаче