Bug Bug 692274, part 4 - Rewrite parsing, emitting and decompiling of let to fix scoping properly (r=jorendorff)

This commit is contained in:
Luke Wagner 2011-10-07 12:02:50 -07:00
Родитель 9e1d472d51
Коммит be823d9e4a
34 изменённых файлов: 2048 добавлений и 902 удалений

Просмотреть файл

@ -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()->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;
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, &regs.fp()->blockChain());
DebugOnly<uintN> blockDepth = OBJ_BLOCK_DEPTH(cx, &regs.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 = &regs.sp[-1];
regs.sp -= GET_UINT16(regs.pc);
if (op == JSOP_LEAVEBLOCKEXPR) {
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 = &regs.sp[-1];
regs.sp -= GET_UINT16(regs.pc);
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,20 +3678,28 @@ 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,
slot, JSPROP_ENUMERATE | JSPROP_PERMANENT,
Shape::HAS_SHORTID, index,
/* allowDictionary = */ false);
const Shape *shape = addPropertyInternal(cx, id, block_getProperty, block_setProperty,
slot, JSPROP_ENUMERATE | JSPROP_PERMANENT,
Shape::HAS_SHORTID, index, spp,
/* allowDictionary = */ false);
if (!shape)
return NULL;
return shape;
@ -4191,8 +4199,11 @@ 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);

Просмотреть файл

@ -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,9 +2285,28 @@ 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,
const char *headChars)
SprintLetBody(JSContext *cx, JSPrinter *jp, SprintStack *ss, jsbytecode *pc, ptrdiff_t bodyLength,
const char *headChars)
{
if (pc[bodyLength] == JSOP_LEAVEBLOCK) {
js_printf(jp, "\tlet (%s) {\n", headChars);
@ -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,22 +3123,11 @@ 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());
}
break;
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,25 +4097,106 @@ 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);
pc = DecompileDestructuring(ss, pc, endpc);
if (!pc)
return NULL;
len = 0;
lval = POP_STR();
op = saveop = JSOP_ENUMELEM;
rval = POP_STR();
if (SN_TYPE(sn) == SRC_DESTRUCT) {
pc = DecompileDestructuring(ss, pc, endpc);
if (!pc)
return NULL;
if (strcmp(rval, forelem_cookie) == 0) {
todo = Sprint(&ss->sprinter, ss_format,
VarPrefix(sn), lval);
lval = POP_STR(); /* Pop the decompiler result. */
rval = POP_STR(); /* Pop the initializer expression. */
// Skip POP so the SRC_FOR_IN code can pop for itself.
if (*pc == JSOP_POP)
len = JSOP_POP_LENGTH;
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. */
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 {
todo = Sprint(&ss->sprinter, "%s%s = %s",
VarPrefix(sn), lval, rval);
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;
}
@ -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());
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;
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');