summaryrefslogtreecommitdiffstats
path: root/js/src/tests/non262/lexical-environment
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/non262/lexical-environment')
-rw-r--r--js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-arguments.js10
-rw-r--r--js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-eval.js51
-rw-r--r--js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-generators.js26
-rw-r--r--js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-if.js42
-rw-r--r--js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-label.js43
-rw-r--r--js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-notapplicable.js14
-rw-r--r--js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-parameter.js21
-rw-r--r--js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-property.js18
-rw-r--r--js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-same-name.js7
-rw-r--r--js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-with.js18
-rw-r--r--js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b.js31
-rw-r--r--js/src/tests/non262/lexical-environment/block-scoped-functions-deprecated-redecl.js78
-rw-r--r--js/src/tests/non262/lexical-environment/block-scoped-functions-hoisted-tdz.js30
-rw-r--r--js/src/tests/non262/lexical-environment/block-scoped-functions-strict.js45
-rw-r--r--js/src/tests/non262/lexical-environment/browser.js0
-rw-r--r--js/src/tests/non262/lexical-environment/bug-1216623.js19
-rw-r--r--js/src/tests/non262/lexical-environment/catch-body.js19
-rw-r--r--js/src/tests/non262/lexical-environment/const-declaration-in-for-loop.js88
-rw-r--r--js/src/tests/non262/lexical-environment/eval-has-lexical-environment.js45
-rw-r--r--js/src/tests/non262/lexical-environment/eval-nondefinable-function.js10
-rw-r--r--js/src/tests/non262/lexical-environment/for-loop-with-bindings-added-at-runtime.js125
-rw-r--r--js/src/tests/non262/lexical-environment/for-loop.js121
-rw-r--r--js/src/tests/non262/lexical-environment/implicit-this-in-with.js18
-rw-r--r--js/src/tests/non262/lexical-environment/nondefinable-function-same-script.js24
-rw-r--r--js/src/tests/non262/lexical-environment/redeclaring-global-properties.js64
-rw-r--r--js/src/tests/non262/lexical-environment/shell.js0
-rw-r--r--js/src/tests/non262/lexical-environment/unscopables-basics.js22
-rw-r--r--js/src/tests/non262/lexical-environment/unscopables-closures.js23
-rw-r--r--js/src/tests/non262/lexical-environment/unscopables-const.js8
-rw-r--r--js/src/tests/non262/lexical-environment/unscopables-delete.js27
-rw-r--r--js/src/tests/non262/lexical-environment/unscopables-getters.js41
-rw-r--r--js/src/tests/non262/lexical-environment/unscopables-global.js18
-rw-r--r--js/src/tests/non262/lexical-environment/unscopables-ignored.js22
-rw-r--r--js/src/tests/non262/lexical-environment/unscopables-miss.js7
-rw-r--r--js/src/tests/non262/lexical-environment/unscopables-mutation-frozen.js18
-rw-r--r--js/src/tests/non262/lexical-environment/unscopables-mutation.js44
-rw-r--r--js/src/tests/non262/lexical-environment/unscopables-proto.js39
-rw-r--r--js/src/tests/non262/lexical-environment/unscopables-proxy.js46
-rw-r--r--js/src/tests/non262/lexical-environment/unscopables-strict.js32
-rw-r--r--js/src/tests/non262/lexical-environment/unscopables-tdz.js9
-rw-r--r--js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b-eval-destructuring.js10
-rw-r--r--js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b-eval-for-of.js12
-rw-r--r--js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b-eval.js21
-rw-r--r--js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b.js114
-rw-r--r--js/src/tests/non262/lexical-environment/with-global-ignores-global-let-variables.js18
45 files changed, 1498 insertions, 0 deletions
diff --git a/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-arguments.js b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-arguments.js
new file mode 100644
index 0000000000..efc263b66f
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-arguments.js
@@ -0,0 +1,10 @@
+// Test that Annex B function interaction with 'arguments'.
+
+(function() {
+ assertEq(typeof arguments, "object");
+ { function arguments() {} }
+ assertEq(typeof arguments, "function");
+})();
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-eval.js b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-eval.js
new file mode 100644
index 0000000000..9f4e4d7fe8
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-eval.js
@@ -0,0 +1,51 @@
+var log = "";
+
+function f() {
+ log += g();
+ function g() { return "outer-g"; }
+
+ var o = { g: function () { return "with-g"; } };
+ with (o) {
+ // Annex B.3.3.3 says g should be set on the nearest VariableEnvironment,
+ // and so should not change o.g.
+ eval(`{
+ function g() { return "eval-g"; }
+ }`);
+ }
+
+ log += g();
+ log += o.g();
+}
+
+f();
+
+function h() {
+ eval(`
+ // Should return true, as var bindings introduced by eval are configurable.
+ log += (delete q);
+ {
+ function q() { log += "q"; }
+ // Should return false, as lexical bindings introduced by eval are not
+ // configurable.
+ log += (delete q);
+ }
+ `);
+ return q;
+}
+
+h()();
+
+function f2() {
+ // Should not throw, just simply not synthesize an Annex B var in the eval
+ // because there's an outer const.
+ eval("{ function a() {} }");
+ const a = 1;
+}
+
+function f3() {
+ // As above, but for let.
+ eval("{ function a() {} }");
+ let a;
+}
+
+reportCompare(log, "outer-geval-gwith-gtruefalseq");
diff --git a/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-generators.js b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-generators.js
new file mode 100644
index 0000000000..dbef82bcfc
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-generators.js
@@ -0,0 +1,26 @@
+// Tests by André Bargull <andrebargull@googlemail.com>
+
+// Annex B.3.3.1
+function f1() {
+ { function* g() {} }
+ assertEq(typeof g, "undefined");
+}
+f1();
+
+// Annex B.3.3.2
+{ function* g() {} }
+assertEq(typeof g, "undefined");
+
+// Annex B.3.3.3
+function f2() {
+ eval("{ function* g() {} }");
+ assertEq(typeof g, "undefined");
+}
+f2();
+
+// Annex B.3.3.3
+eval("{ function* g() {} }");
+assertEq(typeof g, "undefined");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-if.js b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-if.js
new file mode 100644
index 0000000000..ce63aeeea2
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-if.js
@@ -0,0 +1,42 @@
+var log = "";
+
+function f(x) {
+ if (x)
+ function g() { return "g0"; }
+ else
+ function g() { return "g1"; }
+
+ log += g();
+
+ if (x)
+ function g() { return "g2"; }
+ else {
+ }
+
+ log += g();
+
+ if (x) {
+ } else
+ function g() { return "g3"; }
+
+ log += g();
+
+ if (x)
+ function g() { return "g4"; }
+
+ log += g();
+}
+
+f(true);
+f(false);
+
+try {
+ eval(`
+ if (1)
+ l: function foo() {}
+ `);
+} catch (e) {
+ log += "e";
+}
+
+reportCompare(log, "g0g2g2g4g1g1g3g3e");
diff --git a/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-label.js b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-label.js
new file mode 100644
index 0000000000..0fe9f45fca
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-label.js
@@ -0,0 +1,43 @@
+function expectSyntaxError(str) {
+ var threwSyntaxError;
+ try {
+ eval(str);
+ } catch (e) {
+ threwSyntaxError = e instanceof SyntaxError;
+ }
+ assertEq(threwSyntaxError, true);
+
+ try {
+ eval('"use strict";' + str);
+ } catch (e) {
+ threwSyntaxError = e instanceof SyntaxError;
+ }
+ assertEq(threwSyntaxError, true);
+}
+
+function expectSloppyPass(str) {
+ eval(str);
+
+ try {
+ eval('"use strict";' + str);
+ } catch (e) {
+ threwSyntaxError = e instanceof SyntaxError;
+ }
+ assertEq(threwSyntaxError, true);
+}
+
+expectSloppyPass(`l: function f1() {}`);
+expectSloppyPass(`l0: l: function f1() {}`);
+expectSloppyPass(`{ f1(); l: function f1() {} }`);
+expectSloppyPass(`{ f1(); l0: l: function f1() {} }`);
+expectSloppyPass(`{ f1(); l: function f1() { return 42; } } assertEq(f1(), 42);`);
+expectSloppyPass(`eval("fe(); l: function fe() {}")`);
+expectSyntaxError(`if (1) l: function f2() {}`);
+expectSyntaxError(`if (1) {} else l: function f3() {}`);
+expectSyntaxError(`do l: function f4() {} while (0)`);
+expectSyntaxError(`while (0) l: function f5() {}`);
+expectSyntaxError(`for (;;) l: function f6() {}`);
+expectSloppyPass(`switch (1) { case 1: l: function f7() {} }`);
+expectSloppyPass(`switch (1) { case 1: assertEq(f8(), 'f8'); case 2: l: function f8() { return 'f8'; } } assertEq(f8(), 'f8');`);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-notapplicable.js b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-notapplicable.js
new file mode 100644
index 0000000000..4c5e2527a8
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-notapplicable.js
@@ -0,0 +1,14 @@
+// Test that functions in block that do not exhibit Annex B do not override
+// previous functions that do exhibit Annex B.
+
+function f() {
+ var outerX;
+ { function x() {1} outerX = x; }
+ { { function x() {2}; } let x; }
+ { let x; { function x() {3}; } }
+ assertEq(x, outerX);
+}
+f();
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-parameter.js b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-parameter.js
new file mode 100644
index 0000000000..ae7fbe879c
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-parameter.js
@@ -0,0 +1,21 @@
+// Annex B.3.3.1 disallows Annex B lexical function behavior when redeclaring a
+// parameter.
+
+(function(f) {
+ if (true) function f() { }
+ assertEq(f, 123);
+}(123));
+
+(function(f) {
+ { function f() { } }
+ assertEq(f, 123);
+}(123));
+
+(function(f = 123) {
+ assertEq(f, 123);
+ { function f() { } }
+ assertEq(f, 123);
+}());
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-property.js b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-property.js
new file mode 100644
index 0000000000..a295de5081
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-property.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!xulRuntime.shell)
+
+// Define a global getter without a setter.
+Object.defineProperty(this, "x", {
+ get: function () { return "get-x"; },
+ configurable: true
+});
+
+// Simulate loading a 2nd script with evaluate, else we would DEFVAR the x and
+// the above defineProperty would fail in trying to redefine a non-configurable
+// property on the global.
+evaluate(`{
+ function x() { return "fun-x"; }
+}`);
+
+// Annex B is supposed to be like an assignment. Should not blow away the
+// existing setter-less getter.
+reportCompare(x, "get-x");
diff --git a/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-same-name.js b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-same-name.js
new file mode 100644
index 0000000000..b89f91c59a
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-same-name.js
@@ -0,0 +1,7 @@
+{
+ function f() { return "inner"; }
+}
+
+function f() { return "outer"; }
+
+reportCompare(f(), "inner");
diff --git a/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-with.js b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-with.js
new file mode 100644
index 0000000000..d9bdd98dc9
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b-with.js
@@ -0,0 +1,18 @@
+var o = { f: "string-f" };
+with (o) {
+ var desc = Object.getOwnPropertyDescriptor(this, "f");
+ assertEq(desc.value, undefined);
+ assertEq(desc.writable, true);
+ assertEq(desc.enumerable, true);
+ assertEq(desc.configurable, false);
+ function f() {
+ return "fun-f";
+ }
+}
+
+// Annex B explicitly assigns to the nearest VariableEnvironment, so the
+// with-object "o" should have its property unchanged.
+assertEq(o.f, "string-f");
+assertEq(f(), "fun-f");
+
+reportCompare(true, true)
diff --git a/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b.js b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b.js
new file mode 100644
index 0000000000..16c40774ca
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/block-scoped-functions-annex-b.js
@@ -0,0 +1,31 @@
+var log = "";
+
+log += typeof f;
+
+{
+ log += f();
+
+ function f() {
+ return "f1";
+ }
+}
+
+log += f();
+
+function g() {
+ log += typeof h;
+
+ {
+ log += h();
+
+ function h() {
+ return "h1";
+ }
+ }
+
+ log += h();
+}
+
+g();
+
+reportCompare(log, "undefinedf1f1undefinedh1h1");
diff --git a/js/src/tests/non262/lexical-environment/block-scoped-functions-deprecated-redecl.js b/js/src/tests/non262/lexical-environment/block-scoped-functions-deprecated-redecl.js
new file mode 100644
index 0000000000..af32be5d3d
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/block-scoped-functions-deprecated-redecl.js
@@ -0,0 +1,78 @@
+{
+ assertEq(f(), 4);
+ function f() { return 3; }
+ assertEq(f(), 4);
+ function f() { return 4; }
+ assertEq(f(), 4);
+}
+
+// Annex B still works.
+assertEq(f(), 4);
+
+// The same thing with labels.
+{
+ assertEq(f(), 4);
+ function f() { return 3; }
+ assertEq(f(), 4);
+ l: function f() { return 4; }
+ assertEq(f(), 4);
+}
+
+// Annex B still works.
+assertEq(f(), 4);
+
+function test() {
+ {
+ assertEq(f(), 2);
+ function f() { return 1; }
+ assertEq(f(), 2);
+ function f() { return 2; }
+ assertEq(f(), 2);
+ }
+
+ // Annex B still works.
+ assertEq(f(), 2);
+}
+
+test();
+
+var log = '';
+
+try {
+ // Strict mode still cannot redeclare.
+ eval(`"use strict";
+ {
+ function f() { }
+ function f() { }
+ }`);
+} catch (e) {
+ assertEq(e instanceof SyntaxError, true);
+ log += 'e';
+}
+
+try {
+ // Redeclaring an explicitly 'let'-declared binding doesn't work.
+ eval(`{
+ let x = 42;
+ function x() {}
+ }`);
+} catch (e) {
+ assertEq(e instanceof SyntaxError, true);
+ log += 'e';
+}
+
+try {
+ // Redeclaring an explicitly 'const'-declared binding doesn't work.
+ eval(`{
+ const x = 42;
+ function x() {}
+ }`);
+} catch (e) {
+ assertEq(e instanceof SyntaxError, true);
+ log += 'e';
+}
+
+assertEq(log, 'eee');
+
+if ('reportCompare' in this)
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/lexical-environment/block-scoped-functions-hoisted-tdz.js b/js/src/tests/non262/lexical-environment/block-scoped-functions-hoisted-tdz.js
new file mode 100644
index 0000000000..e5f9baf446
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/block-scoped-functions-hoisted-tdz.js
@@ -0,0 +1,30 @@
+var log = "";
+try {
+ (function() {
+ {
+ let y = f();
+ function f() { y; }
+ }
+ })()
+} catch (e) {
+ log += e instanceof ReferenceError;
+}
+
+try {
+ function f() {
+ switch (1) {
+ case 0:
+ let x;
+ case 1:
+ (function() { x; })();
+ }
+ }
+ f();
+} catch (e) {
+ log += e instanceof ReferenceError;
+}
+
+assertEq(log, "truetrue");
+
+if ("reportCompare" in this)
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/lexical-environment/block-scoped-functions-strict.js b/js/src/tests/non262/lexical-environment/block-scoped-functions-strict.js
new file mode 100644
index 0000000000..2b780d7dd1
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/block-scoped-functions-strict.js
@@ -0,0 +1,45 @@
+"use strict"
+
+var log = "";
+
+function f() {
+ return "f0";
+}
+
+log += f();
+
+{
+ log += f();
+
+ function f() {
+ return "f1";
+ }
+
+ log += f();
+}
+
+log += f();
+
+function g() {
+ function h() {
+ return "h0";
+ }
+
+ log += h();
+
+ {
+ log += h();
+
+ function h() {
+ return "h1";
+ }
+
+ log += h();
+ }
+
+ log += h();
+}
+
+g();
+
+reportCompare(log, "f0f1f1f0h0h1h1h0");
diff --git a/js/src/tests/non262/lexical-environment/browser.js b/js/src/tests/non262/lexical-environment/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/browser.js
diff --git a/js/src/tests/non262/lexical-environment/bug-1216623.js b/js/src/tests/non262/lexical-environment/bug-1216623.js
new file mode 100644
index 0000000000..cbdbe9722e
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/bug-1216623.js
@@ -0,0 +1,19 @@
+// Scoping in the head of for(let;;) statements.
+
+let x = 0;
+for (let i = 0, a = () => i; i < 4; i++) {
+ assertEq(i, x++);
+ assertEq(a(), 0);
+}
+assertEq(x, 4);
+
+x = 11;
+let q = 0;
+for (let {[++q]: r} = [0, 11, 22], s = () => r; r < 13; r++) {
+ assertEq(r, x++);
+ assertEq(s(), 11);
+}
+assertEq(x, 13);
+assertEq(q, 1);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/catch-body.js b/js/src/tests/non262/lexical-environment/catch-body.js
new file mode 100644
index 0000000000..43b9d2bfe3
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/catch-body.js
@@ -0,0 +1,19 @@
+function f() {
+ var probeParam, probeBlock;
+ let x = 'outside';
+
+ try {
+ throw [];
+ } catch ([_ = probeParam = function() { return x; }]) {
+ probeBlock = function() { return x; };
+ let x = 'inside';
+ }
+
+ assertEq(probeBlock(), 'inside');
+ assertEq(probeParam(), 'outside');
+}
+
+f();
+
+if (typeof reportCompare === 'function')
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/lexical-environment/const-declaration-in-for-loop.js b/js/src/tests/non262/lexical-environment/const-declaration-in-for-loop.js
new file mode 100644
index 0000000000..4001220ba3
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/const-declaration-in-for-loop.js
@@ -0,0 +1,88 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+var gTestfile = "const-declaration-in-for-loop.js";
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1146644;
+var summary =
+ "Don't crash compiling a non-body-level for-loop whose loop declaration is " +
+ "a const";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+// Don't attempt execution as a script if we can't properly emulate it. We
+// could perhaps use eval, but eval, while also doing global execution, is its
+// own can of messiness. Ongoing work on for-loop scoping for lexical
+// declarations will likely make these tests redundant with other tests to be
+// added, anyway, in the very short term.
+var executeGlobalScript = typeof evaluate === "function"
+ ? evaluate
+ : function(s) {};
+
+for (const a1 = 3; false; )
+ continue;
+
+Function(`for (const a2 = 3; false; )
+ continue;
+ `)();
+
+if (true)
+{
+ for (const a3 = 3; false; )
+ continue;
+}
+
+Function(`if (true)
+ {
+ for (const a4 = 3; false; )
+ continue;
+ }`)();
+
+executeGlobalScript(`for (const a5 of [])
+ continue;`);
+
+Function(`for (const a6 of [])
+ continue;`)();
+
+executeGlobalScript(`if (true)
+ {
+ for (const a7 of [])
+ continue;
+ }`);
+
+Function(`if (true)
+ {
+ for (const a8 of [])
+ continue;
+ }`)();
+
+executeGlobalScript(`for (const a9 in {})
+ continue;`);
+
+Function(`for (const a10 in {})
+ continue;`)();
+
+executeGlobalScript(`if (true)
+ {
+ for (const a11 in {})
+ continue;
+ }`);
+
+Function(`if (true)
+ {
+ for (const a12 in {})
+ continue;
+ }`)();
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
+
+print("Tests complete");
diff --git a/js/src/tests/non262/lexical-environment/eval-has-lexical-environment.js b/js/src/tests/non262/lexical-environment/eval-has-lexical-environment.js
new file mode 100644
index 0000000000..16ffb06d19
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/eval-has-lexical-environment.js
@@ -0,0 +1,45 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+var gTestfile = "eval-has-lexical-environment.js"
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1193583;
+var summary =
+ "Eval always has a lexical environment";
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+eval(`
+let foo = 42;
+const kay = foo;
+var bar = 84;
+function f() {
+ return foo + kay;
+}
+ `);
+
+(1, eval)(`
+let foo2 = 42;
+const kay2 = foo2;
+`);
+
+// Lexical declarations should not have escaped eval.
+assertEq(typeof foo, "undefined");
+assertEq(typeof kay, "undefined");
+assertEq(typeof foo2, "undefined");
+assertEq(typeof kay2, "undefined");
+
+// Eval'd functions can close over lexical bindings.
+assertEq(f(), 84);
+
+// Var can escape direct eval.
+assertEq(bar, 84);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
+
+print("Tests complete");
diff --git a/js/src/tests/non262/lexical-environment/eval-nondefinable-function.js b/js/src/tests/non262/lexical-environment/eval-nondefinable-function.js
new file mode 100644
index 0000000000..bd5dcf7978
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/eval-nondefinable-function.js
@@ -0,0 +1,10 @@
+try {
+ eval("var shouldNotBeDefined1; function NaN(){}; var shouldNotBeDefined2;");
+} catch (e) {
+}
+
+assertEq(Object.getOwnPropertyDescriptor(this, 'shouldNotBeDefined2'), undefined);
+assertEq(Object.getOwnPropertyDescriptor(this, 'shouldNotBeDefined1'), undefined);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/lexical-environment/for-loop-with-bindings-added-at-runtime.js b/js/src/tests/non262/lexical-environment/for-loop-with-bindings-added-at-runtime.js
new file mode 100644
index 0000000000..f6abf3d0cd
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/for-loop-with-bindings-added-at-runtime.js
@@ -0,0 +1,125 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+var gTestfile = "for-loop-with-bindings-added-at-runtime.js";
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1149797;
+var summary =
+ "Don't assert when freshening the scope chain for a for-loop whose head " +
+ "contains a lexical declaration, where the loop body might add more " +
+ "bindings at runtime";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+for (let x = 0; x < 9; ++x)
+ eval("var y");
+
+{
+ for (let x = 0; x < 9; ++x)
+ eval("var y");
+}
+
+function f1()
+{
+ for (let x = 0; x < 9; ++x)
+ eval("var y");
+}
+f1();
+
+function f2()
+{
+ {
+ for (let x = 0; x < 9; ++x)
+ eval("var y");
+ }
+}
+f2();
+
+for (let x = 0; x < 9; ++x)
+{
+ // deliberately inside a block statement
+ eval("var y");
+}
+
+{
+ for (let x = 0; x < 9; ++x)
+ {
+ // deliberately inside a block statement
+ eval("var y");
+ }
+}
+
+function g1()
+{
+ for (let x = 0; x < 9; ++x)
+ {
+ // deliberately inside a block statement
+ eval("var y");
+ }
+}
+g1();
+
+function g2()
+{
+ {
+ for (let x = 0; x < 9; ++x)
+ {
+ // deliberately inside a block statement
+ eval("var y");
+ }
+ }
+}
+g2();
+
+for (let x = 0; x < 9; ++x) {
+ (function() {
+ eval("var y");
+ })();
+}
+
+{
+ for (let x = 0; x < 9; ++x)
+ {
+ // deliberately inside a block statement
+ (function() {
+ eval("var y");
+ })();
+ }
+}
+
+function h1()
+{
+ for (let x = 0; x < 9; ++x)
+ {
+ // deliberately inside a block statement
+ (function() {
+ eval("var y");
+ })();
+ }
+}
+h1();
+
+function h2()
+{
+ {
+ for (let x = 0; x < 9; ++x)
+ {
+ // deliberately inside a block statement
+ (function() { eval("var y"); })();
+ }
+ }
+}
+h2();
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
+
+print("Tests complete");
diff --git a/js/src/tests/non262/lexical-environment/for-loop.js b/js/src/tests/non262/lexical-environment/for-loop.js
new file mode 100644
index 0000000000..60e3799d62
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/for-loop.js
@@ -0,0 +1,121 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ */
+
+var gTestfile = "for-loop.js";
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 985733;
+var summary =
+ "ES6 for-loop semantics for for(;;) loops whose heads contain lexical "
+ "declarations";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+function isError(code, type)
+{
+ try
+ {
+ Function(code);
+ throw new Error("didn't throw");
+ }
+ catch (e)
+ {
+ assertEq(e instanceof type, true,
+ "unexpected error for `" + code + "`: got " + e);
+ }
+}
+
+function isOK(code)
+{
+ Function(code);
+}
+
+isError("for (const x; ; ) ;", SyntaxError);
+isError("for (const x = 5, y; ; ) ;", SyntaxError);
+isError("for (const [z]; ; ) ;", SyntaxError);
+//isError("for (const [z, z]; ; ) ;", SyntaxError);
+//isError("for (const [z, z] = [0, 1]; ; ) ;", SyntaxError);
+
+isOK("for (let x; ; ) ;");
+isOK("for (let x = 5, y; ; ) ;");
+
+// I'm fairly sure this is supposed to work: the negative-lookahead rules in
+// IterationStatement ensure that |for (let| *always* is a loop header starting
+// with a lexical declaration. But I'm not 100% certain, so these tests might
+// need to be fixed when we implement the negative-lookahead restrictions.
+isOK("for (let [z] = [3]; ; ) ;");
+isError("for (let [z, z]; ; ) ;", SyntaxError); // because missing initializer
+
+isError("for (let [z, z] = [0, 1]; ; ) ;", SyntaxError);
+
+// A for-loop with lexical declarations, with a mixture of bindings that are and
+// aren't aliased. (The mixture stress-tests any code that incorrectly assumes
+// all bindings are aliased.)
+var funcs = [];
+for (let [i, j, k] = [0, 1, 2]; i < 10; i++)
+ funcs.push(() => i);
+
+assertEq(funcs[0](), 0);
+assertEq(funcs[1](), 1);
+assertEq(funcs[2](), 2);
+assertEq(funcs[3](), 3);
+assertEq(funcs[4](), 4);
+assertEq(funcs[5](), 5);
+assertEq(funcs[6](), 6);
+assertEq(funcs[7](), 7);
+assertEq(funcs[8](), 8);
+assertEq(funcs[9](), 9);
+
+var outer = "OUTER V IGNORE";
+var save;
+for (let outer = (save = function() { return outer; }); ; )
+ break;
+assertEq(save(), save);
+
+var funcs = [];
+function t(i, name, expect)
+{
+ assertEq(funcs[i].name, name);
+ assertEq(funcs[i](), expect);
+}
+
+if (save() !== "OUTER V IGNORE")
+{
+ var v = "OUTER V IGNORE";
+ var i = 0;
+ for (let v = (funcs.push(function init() { return v; }),
+ 0);
+ v = (funcs.push(function test() { return v; }),
+ v + 1);
+ v = (funcs.push(function incr() { return v; }),
+ v + 1))
+ {
+ v = (funcs.push(function body() { return v; }),
+ v + 1);
+ i++;
+ if (i >= 3)
+ break;
+ }
+ t(0, "init", 0);
+ t(1, "test", 2);
+ t(2, "body", 2);
+ t(3, "incr", 5);
+ t(4, "test", 5);
+ t(5, "body", 5);
+ t(6, "incr", 8);
+ t(7, "test", 8);
+ t(8, "body", 8);
+ assertEq(funcs.length, 9);
+}
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
+
+print("Tests complete");
diff --git a/js/src/tests/non262/lexical-environment/implicit-this-in-with.js b/js/src/tests/non262/lexical-environment/implicit-this-in-with.js
new file mode 100644
index 0000000000..7c112e2447
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/implicit-this-in-with.js
@@ -0,0 +1,18 @@
+// Test that callees that resolve to bindings on the global object or the
+// global lexical environment get an 'undefined' this inside with scopes.
+
+let g = function () { "use strict"; assertEq(this, undefined); }
+function f() { "use strict"; assertEq(this, undefined); }
+
+with ({}) {
+ // f is resolved on the global object
+ f();
+ // g is resolved on the global lexical environment
+ g();
+}
+
+f();
+g();
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/lexical-environment/nondefinable-function-same-script.js b/js/src/tests/non262/lexical-environment/nondefinable-function-same-script.js
new file mode 100644
index 0000000000..5d569fe4a0
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/nondefinable-function-same-script.js
@@ -0,0 +1,24 @@
+// |reftest| skip-if(!xulRuntime.shell)
+
+function assertEvaluateAndIndirectEvalThrows(str) {
+ assertThrowsInstanceOf(() => evaluate(str), TypeError);
+ assertThrowsInstanceOf(() => (1,eval)(str), TypeError);
+}
+
+// Regular vars
+assertEvaluateAndIndirectEvalThrows(`var NaN; function NaN() {}`);
+
+// for-of vars
+assertEvaluateAndIndirectEvalThrows(`for (var NaN of []); function NaN() {}`);
+
+// Annex B.3.3 synthesized vars
+assertEvaluateAndIndirectEvalThrows(`{ function NaN() {} } function NaN() {}`);
+
+// Non-data properties
+Object.defineProperty(this, 'foo', { set: function() {} });
+assertEvaluateAndIndirectEvalThrows(`var foo; function foo() {}`);
+assertEvaluateAndIndirectEvalThrows(`for (var foo of []); function foo() {}`);
+assertEvaluateAndIndirectEvalThrows(`{ function foo() {} } function foo() {}`);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/lexical-environment/redeclaring-global-properties.js b/js/src/tests/non262/lexical-environment/redeclaring-global-properties.js
new file mode 100644
index 0000000000..7fb2f82db1
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/redeclaring-global-properties.js
@@ -0,0 +1,64 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs evaluate
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+// Attempting to lexically redefine a var is a syntax error.
+evaluate("var a;");
+assertThrowsInstanceOf(() => evaluate("let a;"), SyntaxError);
+
+// Attempting to lexically redefine a configurable global property that's not a
+// var is okay.
+this.b = 42;
+assertEq(b, 42);
+evaluate("let b = 17;");
+assertEq(b, 17);
+
+// Attempting to lexically redefine a configurable global property that wasn't
+// a var initially but was later declared as one, isn't okay.
+this.c = 8675309;
+assertEq(c, 8675309);
+evaluate("var c;");
+assertThrowsInstanceOf(() => evaluate("let c;"), SyntaxError);
+
+// Attempting to lexically redefine a var added by eval code isn't okay.
+assertEq(typeof d, "undefined");
+eval("var d = 33;");
+assertEq(d, 33);
+assertThrowsInstanceOf(() => evaluate("let d;"), SyntaxError);
+
+// Attempting to lexically redefine a var added by eval code, then deleted *as a
+// name*, is okay. (The |var| will add the name to the global environment
+// record's [[VarNames]], but deletion will go through the global environment
+// record's DeleteBinding and so will remove it.)
+assertEq(typeof e, "undefined");
+eval("var e = 'ohia';");
+assertEq(e, "ohia");
+delete e;
+assertEq(this.hasOwnProperty("e"), false);
+evaluate("let e = 3.141592654;");
+assertEq(e, 3.141592654);
+
+// Attempting to lexically redefine a var added by eval code, then deleted *as a
+// property*, isn't okay. (Deletion by property doesn't go through the global
+// environment record's DeleteBinding algorithm, and so the name isn't removed
+// from [[VarNames]].) And it remains non-okay even if the var is subsequently
+// deleted as a name, because if the property doesn't exist, it's not removed
+// from [[VarNames]]. But if we add the global property again and then delete
+// by name, it *will* get removed from [[VarNames]].
+assertEq(typeof f, "undefined");
+eval("var f = 8675309;");
+assertEq(f, 8675309);
+delete this.f;
+assertEq(this.hasOwnProperty("f"), false);
+assertThrowsInstanceOf(() => evaluate("let f;"), SyntaxError);
+delete f;
+assertThrowsInstanceOf(() => evaluate("let f;"), SyntaxError);
+this.f = 999;
+assertThrowsInstanceOf(() => evaluate("let f;"), SyntaxError);
+delete f;
+evaluate("let f;");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
+
+print("Tests complete");
diff --git a/js/src/tests/non262/lexical-environment/shell.js b/js/src/tests/non262/lexical-environment/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/shell.js
diff --git a/js/src/tests/non262/lexical-environment/unscopables-basics.js b/js/src/tests/non262/lexical-environment/unscopables-basics.js
new file mode 100644
index 0000000000..4032b27746
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/unscopables-basics.js
@@ -0,0 +1,22 @@
+// Basics of @@unscopables support.
+
+// In with(obj), if obj[@@unscopables][id] is truthy, then the identifier id
+// is not present as a binding in the with-block's scope.
+var x = "global";
+with ({x: "with", [Symbol.unscopables]: {x: true}})
+ assertEq(x, "global");
+
+// But if obj[@@unscopables][id] is false or not present, there is a binding.
+with ({y: "with", z: "with", [Symbol.unscopables]: {y: false}}) {
+ assertEq(y, "with");
+ assertEq(z, "with");
+}
+
+// ToBoolean(obj[@@unscopables][id]) determines whether there's a binding.
+let someValues = [0, -0, NaN, "", undefined, null, "x", {}, []];
+for (let v of someValues) {
+ with ({x: "with", [Symbol.unscopables]: {x: v}})
+ assertEq(x, v ? "global" : "with");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/unscopables-closures.js b/js/src/tests/non262/lexical-environment/unscopables-closures.js
new file mode 100644
index 0000000000..bdade1f113
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/unscopables-closures.js
@@ -0,0 +1,23 @@
+// @@unscopables continues to work after exiting the relevant `with` block,
+// if the environment is captured by a closure.
+
+let env = {
+ x: 9000,
+ [Symbol.unscopables]: {x: true}
+};
+
+function make_adder(x) {
+ with (env)
+ return function (y) { return x + y; };
+}
+assertEq(make_adder(3)(10), 13);
+
+// Same test, but with a bunch of different parts for bad luck
+let x = 500;
+function make_adder_with_eval() {
+ with (env)
+ return eval('y => eval("x + y")');
+}
+assertEq(make_adder_with_eval()(10), 510);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/unscopables-const.js b/js/src/tests/non262/lexical-environment/unscopables-const.js
new file mode 100644
index 0000000000..7e1d0e07c6
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/unscopables-const.js
@@ -0,0 +1,8 @@
+// @@unscopables prevents a property from having any effect on assigning to a
+// const binding (which is an error).
+
+const x = 1;
+with ({x: 1, [Symbol.unscopables]: {x: true}})
+ assertThrowsInstanceOf(() => {x = 2;}, TypeError);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/unscopables-delete.js b/js/src/tests/non262/lexical-environment/unscopables-delete.js
new file mode 100644
index 0000000000..3cd296f4b9
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/unscopables-delete.js
@@ -0,0 +1,27 @@
+// If obj[@@unscopables][id], then `delete id` works across `with (obj)` scope.
+
+this.niche = 7;
+let obj = { niche: 8, [Symbol.unscopables]: { niche: true } };
+with (obj) {
+ delete niche;
+}
+
+assertEq(obj.niche, 8);
+assertEq("niche" in this, false);
+
+// Same thing, but delete a variable introduced by sloppy direct eval.
+this.niche = 9;
+function f() {
+ eval("var niche = 10;");
+ with (obj) {
+ assertEq(niche, 10);
+ delete niche;
+ }
+ assertEq(niche, 9);
+}
+
+// Of course none of this affects a qualified delete.
+assertEq(delete this.niche, true);
+assertEq("niche" in this, false);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/unscopables-getters.js b/js/src/tests/non262/lexical-environment/unscopables-getters.js
new file mode 100644
index 0000000000..1360787983
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/unscopables-getters.js
@@ -0,0 +1,41 @@
+// @@unscopables checks can call getters.
+
+// The @@unscopables property itself can be a getter.
+let hit1 = 0;
+let x = "global x";
+let env1 = {
+ x: "env1.x",
+ get [Symbol.unscopables]() {
+ hit1++;
+ return {x: true};
+ }
+};
+with (env1)
+ assertEq(x, "global x");
+assertEq(hit1, 1);
+
+// It can throw; the exception is propagated out.
+function Fit() {}
+with ({x: 0, get [Symbol.unscopables]() { throw new Fit; }})
+ assertThrowsInstanceOf(() => x, Fit);
+
+// Individual properties on the @@unscopables object can have getters.
+let hit2 = 0;
+let env2 = {
+ x: "env2.x",
+ [Symbol.unscopables]: {
+ get x() {
+ hit2++;
+ return true;
+ }
+ }
+};
+with (env2)
+ assertEq(x, "global x");
+assertEq(hit2, 1);
+
+// And they can throw.
+with ({x: 0, [Symbol.unscopables]: {get x() { throw new Fit; }}})
+ assertThrowsInstanceOf(() => x, Fit);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/unscopables-global.js b/js/src/tests/non262/lexical-environment/unscopables-global.js
new file mode 100644
index 0000000000..1aa4a52bda
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/unscopables-global.js
@@ -0,0 +1,18 @@
+// @@unscopables does not affect the global environment.
+
+this.x = "global property x";
+let y = "global lexical y";
+this[Symbol.unscopables] = {x: true, y: true};
+assertEq(x, "global property x");
+assertEq(y, "global lexical y");
+assertEq(eval("x"), "global property x");
+assertEq(eval("y"), "global lexical y");
+
+// But it does affect `with` statements targeting the global object.
+{
+ let x = "local x";
+ with (this)
+ assertEq(x, "local x");
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/unscopables-ignored.js b/js/src/tests/non262/lexical-environment/unscopables-ignored.js
new file mode 100644
index 0000000000..08b042a3b5
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/unscopables-ignored.js
@@ -0,0 +1,22 @@
+// In these cases, @@unscopables should not be consulted.
+
+// Because obj has no properties `assertEq` or `x`,
+// obj[@@unscopables] is not checked here:
+var obj = {
+ get [Symbol.unscopables]() {
+ throw "tried to read @@unscopables";
+ }
+};
+var x = 3;
+with (obj)
+ assertEq(x, 3);
+
+// If @@unscopables is present but not an object, it is ignored:
+for (let nonObject of [undefined, null, "nothing", Symbol.for("moon")]) {
+ let y = 4;
+ let obj2 = {[Symbol.unscopables]: nonObject, y: 5};
+ with (obj2)
+ assertEq(y, 5);
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/unscopables-miss.js b/js/src/tests/non262/lexical-environment/unscopables-miss.js
new file mode 100644
index 0000000000..b86d510787
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/unscopables-miss.js
@@ -0,0 +1,7 @@
+// Trying to access a binding that doesn't exist due to @@unscopables
+// is a ReferenceError.
+
+with ({x: 1, [Symbol.unscopables]: {x: true}})
+ assertThrowsInstanceOf(() => x, ReferenceError);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/unscopables-mutation-frozen.js b/js/src/tests/non262/lexical-environment/unscopables-mutation-frozen.js
new file mode 100644
index 0000000000..632785c05a
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/unscopables-mutation-frozen.js
@@ -0,0 +1,18 @@
+// When env[@@unscopables].x changes, bindings can appear even if env is inextensible.
+
+let x = "global";
+let unscopables = {x: true};
+let env = Object.create(null);
+env[Symbol.unscopables] = unscopables;
+env.x = "object";
+Object.freeze(env);
+
+for (let i = 0; i < 1004; i++) {
+ if (i === 1000)
+ unscopables.x = false;
+ with (env) {
+ assertEq(x, i < 1000 ? "global" : "object");
+ }
+}
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/unscopables-mutation.js b/js/src/tests/non262/lexical-environment/unscopables-mutation.js
new file mode 100644
index 0000000000..2f35e1dd3c
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/unscopables-mutation.js
@@ -0,0 +1,44 @@
+// When obj[@@unscopables].x changes, bindings appear and disappear accordingly.
+
+let x = "global";
+function getX() { return x; }
+
+let unscopables = {x: true};
+let obj = {x: "obj", [Symbol.unscopables]: unscopables};
+
+with (obj) {
+ assertEq(x, "global");
+ x = "global-1";
+ assertEq(x, "global-1");
+ assertEq(obj.x, "obj");
+
+ unscopables.x = false; // suddenly x appears in the with-environment
+
+ assertEq(x, "obj");
+ x = "obj-1";
+ assertEq(getX(), "global-1"); // unchanged
+ assertEq(obj.x, "obj-1");
+
+ unscopables.x = true; // *poof*
+
+ assertEq(x, "global-1");
+ x = "global-2";
+ assertEq(getX(), "global-2");
+ assertEq(obj.x, "obj-1"); // unchanged
+
+ // The determination of which binding is assigned happens when the LHS of
+ // assignment is evaluated, before the RHS. This is observable if we make
+ // the binding appear or disappear during evaluation of the RHS, before
+ // assigning.
+ x = (unscopables.x = false, "global-3");
+ assertEq(getX(), "global-3");
+ assertEq(obj.x, "obj-1");
+
+ x = (unscopables.x = true, "obj-2");
+ assertEq(getX(), "global-3");
+ assertEq(obj.x, "obj-2");
+}
+
+assertEq(x, "global-3");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/unscopables-proto.js b/js/src/tests/non262/lexical-environment/unscopables-proto.js
new file mode 100644
index 0000000000..dbbfb712d3
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/unscopables-proto.js
@@ -0,0 +1,39 @@
+// @@unscopables treats properties found on prototype chains the same as other
+// properties.
+
+const x = "global x";
+const y = "global y";
+
+// obj[@@unscopables].x works when obj.x is inherited via the prototype chain.
+let proto = {x: "object x", y: "object y"};
+let env = Object.create(proto);
+env[Symbol.unscopables] = {x: true, y: false};
+with (env) {
+ assertEq(x, "global x");
+ assertEq(delete x, false);
+ assertEq(y, "object y");
+}
+assertEq(env.x, "object x");
+
+// @@unscopables works if is inherited via the prototype chain.
+env = {
+ x: "object",
+ [Symbol.unscopables]: {x: true, y: true}
+};
+for (let i = 0; i < 50; i++)
+ env = Object.create(env);
+env.y = 1;
+with (env) {
+ assertEq(x, "global x");
+ assertEq(y, "global y");
+}
+
+// @@unscopables works if the obj[@@unscopables][id] property is inherited.
+env = {
+ x: "object",
+ [Symbol.unscopables]: Object.create({x: true})
+};
+with (env)
+ assertEq(x, "global x");
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/unscopables-proxy.js b/js/src/tests/non262/lexical-environment/unscopables-proxy.js
new file mode 100644
index 0000000000..fcf241ee46
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/unscopables-proxy.js
@@ -0,0 +1,46 @@
+// Object operations are performed in the right order, as observed by proxies.
+
+let log = [];
+function LoggingProxyHandlerWrapper(name, handler={}) {
+ return new Proxy(handler, {
+ get(t, id) {
+ let method = handler[id];
+ return function (...args) {
+ log.push([name + "." + id, ...args.filter(v => typeof v !== "object")]);
+ if (method === undefined)
+ return Reflect[id].apply(null, args);
+ return method.apply(this, args);
+ };
+ }
+ });
+}
+
+function LoggingProxy(name, target) {
+ return new Proxy(target, new LoggingProxyHandlerWrapper(name));
+}
+
+let proto = {x: 44};
+let proto_proxy = new LoggingProxy("proto", proto);
+let unscopables = {x: true};
+let unscopables_proxy = new LoggingProxy("unscopables", {x: true});
+let env = Object.create(proto_proxy, {
+ [Symbol.unscopables]: { value: unscopables_proxy }
+});
+let env_proxy = new LoggingProxy("env", env);
+
+let x = 11;
+function f() {
+ with (env_proxy)
+ return x;
+}
+
+assertEq(f(), 11);
+
+assertDeepEq(log, [
+ ["env.has", "x"],
+ ["proto.has", "x"],
+ ["env.get", Symbol.unscopables],
+ ["unscopables.get", "x"]
+]);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/unscopables-strict.js b/js/src/tests/non262/lexical-environment/unscopables-strict.js
new file mode 100644
index 0000000000..fd0413ed7b
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/unscopables-strict.js
@@ -0,0 +1,32 @@
+// Strict assignment to the name of a property that's masked by @@unscopables
+// throws a ReferenceError.
+
+let env = {k: 1};
+let f;
+with (env) {
+ f = function () {
+ "use strict";
+ k = 2;
+ };
+}
+
+f();
+assertEq(env.k, 2);
+
+env[Symbol.unscopables] = {k: true};
+assertThrowsInstanceOf(f, ReferenceError);
+
+// @@unscopables is tested when the LHS of assignment is evaluated, so there is
+// no effect on the assignment if it is changed while evaluating the RHS.
+let g;
+with (env) {
+ g = function () {
+ "use strict";
+ k = (env[Symbol.unscopables].k = true, 3);
+ }
+}
+env[Symbol.unscopables].k = false;
+g();
+assertEq(env.k, 3);
+
+reportCompare(0, 0);
diff --git a/js/src/tests/non262/lexical-environment/unscopables-tdz.js b/js/src/tests/non262/lexical-environment/unscopables-tdz.js
new file mode 100644
index 0000000000..ce6b1df929
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/unscopables-tdz.js
@@ -0,0 +1,9 @@
+// Accessing an uninitialized variable due to @@unscopables is still a ReferenceError.
+
+with ({x: 1, [Symbol.unscopables]: {x: true}})
+ assertThrowsInstanceOf(() => x, ReferenceError);
+
+let x;
+
+reportCompare(0, 0);
+
diff --git a/js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b-eval-destructuring.js b/js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b-eval-destructuring.js
new file mode 100644
index 0000000000..06174013e8
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b-eval-destructuring.js
@@ -0,0 +1,10 @@
+// |reftest| skip-if(!xulRuntime.shell)
+
+assertThrowsInstanceOf(() => evaluate(`try { throw {} } catch ({e}) { var e; }`), SyntaxError);
+assertThrowsInstanceOf(() => evaluate(`try { throw {} } catch ({e}) { eval('var e'); }`), SyntaxError);
+
+assertThrowsInstanceOf(() => new Function(`try { throw {} } catch ({e}) { var e; }`), SyntaxError);
+assertThrowsInstanceOf(new Function(`try { throw {} } catch ({e}) { eval('var e'); }`), SyntaxError);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b-eval-for-of.js b/js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b-eval-for-of.js
new file mode 100644
index 0000000000..b433b68b6e
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b-eval-for-of.js
@@ -0,0 +1,12 @@
+// |reftest| skip-if(!xulRuntime.shell)
+
+evaluate(`
+ try { throw null; } catch (e) { eval("for (var e of []) {}") }
+`);
+
+new Function(`
+ try { throw null; } catch (e) { eval("for (var e of []) {}") }
+`)();
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b-eval.js b/js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b-eval.js
new file mode 100644
index 0000000000..e380846e25
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b-eval.js
@@ -0,0 +1,21 @@
+// Tests annex B.3.5 that introduces a var via direct eval.
+
+var x = "global-x";
+var log = "";
+
+// Tests that direct eval works.
+function g() {
+ try { throw 8; } catch (x) {
+ eval("var x = 42;");
+ log += x;
+ }
+ x = "g";
+ log += x;
+}
+g();
+
+assertEq(x, "global-x");
+assertEq(log, "42g");
+
+if ("reportCompare" in this)
+ reportCompare(true, true)
diff --git a/js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b.js b/js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b.js
new file mode 100644
index 0000000000..6e6a24f213
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/var-in-catch-body-annex-b.js
@@ -0,0 +1,114 @@
+// Tests annex B.3.5.
+
+assertThrowsInstanceOf(function () {
+ eval(`
+ function f() {
+ let x;
+ try {} catch (x) {
+ var x;
+ }
+ }
+ `);
+}, SyntaxError);
+
+assertThrowsInstanceOf(function () {
+ eval(`
+ function f() {
+ try {} catch (x) {
+ let y;
+ var y;
+ }
+ }
+ `);
+}, SyntaxError);
+
+assertThrowsInstanceOf(function () {
+ eval(`
+ function f() {
+ try {} catch (x) {
+ let x;
+ }
+ }
+ `);
+}, SyntaxError);
+
+assertThrowsInstanceOf(function () {
+ eval(`
+ function f() {
+ try {} catch (x) {
+ const x;
+ }
+ }
+ `);
+}, SyntaxError);
+
+// Tests that redeclaring a var inside the catch is not allowed if there's a
+// body-level lexical.
+assertThrowsInstanceOf(function () {
+ eval(`
+ let x;
+ try {} catch (x) {
+ var x;
+ }
+ `);
+}, SyntaxError);
+
+var log = '';
+var x = 'global-x';
+
+function g() {
+ x = 'g';
+ try { throw 8; } catch (x) {
+ var x = 42;
+ log += x;
+ }
+ log += x;
+}
+g();
+
+// Tests that var declaration is allowed in for-in head.
+function h0() {
+ try {} catch (e) {
+ for (var e in {});
+ }
+}
+h0();
+
+// Tests that var declaration is allowed in C-for head.
+function h1() {
+ try {} catch (e) {
+ for (var e;;);
+ }
+}
+h1();
+
+// Tests that var declaration is allowed in for-of head.
+function h2() {
+ try {} catch (e) {
+ for (var e of {});
+ }
+}
+h2();
+
+// Tests that redeclaring a var inside the catch is allowed.
+function h3() {
+ var e;
+ try {} catch (e) {
+ var e;
+ }
+}
+h3();
+
+if (typeof evaluate === "function") {
+ assertThrowsInstanceOf(function () {
+ evaluate(`
+ let y;
+ try {} catch (y) { var y; }
+ `);
+ }, SyntaxError);
+}
+
+assertEq(log, "42g");
+
+if ("reportCompare" in this)
+ reportCompare(true, true)
diff --git a/js/src/tests/non262/lexical-environment/with-global-ignores-global-let-variables.js b/js/src/tests/non262/lexical-environment/with-global-ignores-global-let-variables.js
new file mode 100644
index 0000000000..042f92f688
--- /dev/null
+++ b/js/src/tests/non262/lexical-environment/with-global-ignores-global-let-variables.js
@@ -0,0 +1,18 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+let v = "global-v";
+
+function f(v, global)
+{
+ with (global)
+ return v;
+}
+
+assertEq(f("argument-v", this), "argument-v",
+ "let-var shouldn't appear in global for |with| purposes");
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
+
+print("Tests complete");