f6c6ae9034
Annex B.3.3 of the spec requires that sloppy-mode block-scoped functions declared by "eval" are hoisted unless doing so would cause an early error (which is to say, conflict with a lexical declaration). This patch amends the check for conflicting declarations to include those outside of the eval itself. BUG=v8:4468, v8:4479 Review-Url: https://codereview.chromium.org/2112163002 Cr-Commit-Position: refs/heads/master@{#37783}
657 lines
13 KiB
JavaScript
657 lines
13 KiB
JavaScript
// Copyright 2015 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// Test Annex B 3.3 semantics for functions declared in blocks in sloppy mode.
|
|
// http://www.ecma-international.org/ecma-262/6.0/#sec-block-level-function-declarations-web-legacy-compatibility-semantics
|
|
|
|
(function overridingLocalFunction() {
|
|
var x = [];
|
|
assertEquals('function', typeof f);
|
|
function f() {
|
|
x.push(1);
|
|
}
|
|
f();
|
|
{
|
|
f();
|
|
function f() {
|
|
x.push(2);
|
|
}
|
|
f();
|
|
}
|
|
f();
|
|
{
|
|
f();
|
|
function f() {
|
|
x.push(3);
|
|
}
|
|
f();
|
|
}
|
|
f();
|
|
assertArrayEquals([1, 2, 2, 2, 3, 3, 3], x);
|
|
})();
|
|
|
|
(function newFunctionBinding() {
|
|
var x = [];
|
|
assertEquals('undefined', typeof f);
|
|
{
|
|
f();
|
|
function f() {
|
|
x.push(2);
|
|
}
|
|
f();
|
|
}
|
|
f();
|
|
{
|
|
f();
|
|
function f() {
|
|
x.push(3);
|
|
}
|
|
f();
|
|
}
|
|
f();
|
|
assertArrayEquals([2, 2, 2, 3, 3, 3], x);
|
|
})();
|
|
|
|
(function shadowingLetDoesntBind() {
|
|
let f = 1;
|
|
assertEquals(1, f);
|
|
{
|
|
let y = 3;
|
|
function f() {
|
|
y = 2;
|
|
}
|
|
f();
|
|
assertEquals(2, y);
|
|
}
|
|
assertEquals(1, f);
|
|
})();
|
|
|
|
(function shadowingClassDoesntBind() {
|
|
class f { }
|
|
assertEquals('class f { }', f.toString());
|
|
{
|
|
let y = 3;
|
|
function f() {
|
|
y = 2;
|
|
}
|
|
f();
|
|
assertEquals(2, y);
|
|
}
|
|
assertEquals('class f { }', f.toString());
|
|
})();
|
|
|
|
(function shadowingConstDoesntBind() {
|
|
const f = 1;
|
|
assertEquals(1, f);
|
|
{
|
|
let y = 3;
|
|
function f() {
|
|
y = 2;
|
|
}
|
|
f();
|
|
assertEquals(2, y);
|
|
}
|
|
assertEquals(1, f);
|
|
})();
|
|
|
|
(function shadowingVarBinds() {
|
|
var f = 1;
|
|
assertEquals(1, f);
|
|
{
|
|
let y = 3;
|
|
function f() {
|
|
y = 2;
|
|
}
|
|
f();
|
|
assertEquals(2, y);
|
|
}
|
|
assertEquals('function', typeof f);
|
|
})();
|
|
|
|
(function complexParams(a = 0) {
|
|
{
|
|
let y = 3;
|
|
function f(b = 0) {
|
|
y = 2;
|
|
}
|
|
f();
|
|
assertEquals(2, y);
|
|
}
|
|
assertEquals('function', typeof f);
|
|
})();
|
|
|
|
(function complexVarParams(a = 0) {
|
|
var f;
|
|
{
|
|
let y = 3;
|
|
function f(b = 0) {
|
|
y = 2;
|
|
}
|
|
f();
|
|
assertEquals(2, y);
|
|
}
|
|
assertEquals('function', typeof f);
|
|
})();
|
|
|
|
(function conditional() {
|
|
if (true) {
|
|
function f() { return 1; }
|
|
} else {
|
|
function f() { return 2; }
|
|
}
|
|
assertEquals(1, f());
|
|
|
|
if (false) {
|
|
function g() { return 1; }
|
|
} else {
|
|
function g() { return 2; }
|
|
}
|
|
assertEquals(2, g());
|
|
})();
|
|
|
|
(function skipExecution() {
|
|
{
|
|
function f() { return 1; }
|
|
}
|
|
assertEquals(1, f());
|
|
{
|
|
function f() { return 2; }
|
|
}
|
|
assertEquals(2, f());
|
|
L: {
|
|
assertEquals(3, f());
|
|
break L;
|
|
function f() { return 3; }
|
|
}
|
|
assertEquals(2, f());
|
|
})();
|
|
|
|
(function executionOrder() {
|
|
function getOuter() {
|
|
return f;
|
|
}
|
|
assertEquals('undefined', typeof getOuter());
|
|
|
|
{
|
|
assertEquals('function', typeof f);
|
|
assertEquals('undefined', typeof getOuter());
|
|
function f () {}
|
|
assertEquals('function', typeof f);
|
|
assertEquals('function', typeof getOuter());
|
|
}
|
|
|
|
assertEquals('function', typeof getOuter());
|
|
})();
|
|
|
|
(function reassignBindings() {
|
|
function getOuter() {
|
|
return f;
|
|
}
|
|
assertEquals('undefined', typeof getOuter());
|
|
|
|
{
|
|
assertEquals('function', typeof f);
|
|
assertEquals('undefined', typeof getOuter());
|
|
f = 1;
|
|
assertEquals('number', typeof f);
|
|
assertEquals('undefined', typeof getOuter());
|
|
function f () {}
|
|
assertEquals('number', typeof f);
|
|
assertEquals('number', typeof getOuter());
|
|
f = '';
|
|
assertEquals('string', typeof f);
|
|
assertEquals('number', typeof getOuter());
|
|
}
|
|
|
|
assertEquals('number', typeof getOuter());
|
|
})();
|
|
|
|
// Test that shadowing arguments is fine
|
|
(function shadowArguments(x) {
|
|
assertArrayEquals([1], arguments);
|
|
{
|
|
assertEquals('function', typeof arguments);
|
|
function arguments() {}
|
|
assertEquals('function', typeof arguments);
|
|
}
|
|
assertEquals('function', typeof arguments);
|
|
})(1);
|
|
|
|
|
|
// Don't shadow simple parameter
|
|
(function shadowingParameterDoesntBind(x) {
|
|
assertEquals(1, x);
|
|
{
|
|
function x() {}
|
|
}
|
|
assertEquals(1, x);
|
|
})(1);
|
|
|
|
// Don't shadow complex parameter
|
|
(function shadowingDefaultParameterDoesntBind(x = 0) {
|
|
assertEquals(1, x);
|
|
{
|
|
function x() {}
|
|
}
|
|
assertEquals(1, x);
|
|
})(1);
|
|
|
|
// Don't shadow nested complex parameter
|
|
(function shadowingNestedParameterDoesntBind([[x]]) {
|
|
assertEquals(1, x);
|
|
{
|
|
function x() {}
|
|
}
|
|
assertEquals(1, x);
|
|
})([[1]]);
|
|
|
|
// Don't shadow rest parameter
|
|
(function shadowingRestParameterDoesntBind(...x) {
|
|
assertArrayEquals([1], x);
|
|
{
|
|
function x() {}
|
|
}
|
|
assertArrayEquals([1], x);
|
|
})(1);
|
|
|
|
// Don't shadow complex rest parameter
|
|
(function shadowingComplexRestParameterDoesntBind(...[x]) {
|
|
assertArrayEquals(1, x);
|
|
{
|
|
function x() {}
|
|
}
|
|
assertArrayEquals(1, x);
|
|
})(1);
|
|
|
|
// Previous tests with a var declaration thrown in.
|
|
// Don't shadow simple parameter
|
|
(function shadowingVarParameterDoesntBind(x) {
|
|
var x;
|
|
assertEquals(1, x);
|
|
{
|
|
function x() {}
|
|
}
|
|
assertEquals(1, x);
|
|
})(1);
|
|
|
|
// Don't shadow complex parameter
|
|
(function shadowingVarDefaultParameterDoesntBind(x = 0) {
|
|
var x;
|
|
assertEquals(1, x);
|
|
{
|
|
function x() {}
|
|
}
|
|
assertEquals(1, x);
|
|
})(1);
|
|
|
|
// Don't shadow nested complex parameter
|
|
(function shadowingVarNestedParameterDoesntBind([[x]]) {
|
|
var x;
|
|
assertEquals(1, x);
|
|
{
|
|
function x() {}
|
|
}
|
|
assertEquals(1, x);
|
|
})([[1]]);
|
|
|
|
// Don't shadow rest parameter
|
|
(function shadowingVarRestParameterDoesntBind(...x) {
|
|
var x;
|
|
assertArrayEquals([1], x);
|
|
{
|
|
function x() {}
|
|
}
|
|
assertArrayEquals([1], x);
|
|
})(1);
|
|
|
|
// Don't shadow complex rest parameter
|
|
(function shadowingVarComplexRestParameterDoesntBind(...[x]) {
|
|
var x;
|
|
assertArrayEquals(1, x);
|
|
{
|
|
function x() {}
|
|
}
|
|
assertArrayEquals(1, x);
|
|
})(1);
|
|
|
|
|
|
// Hoisting is not affected by other simple parameters
|
|
(function irrelevantParameterBinds(y, z) {
|
|
assertEquals(undefined, x);
|
|
{
|
|
function x() {}
|
|
}
|
|
assertEquals('function', typeof x);
|
|
})(1);
|
|
|
|
// Hoisting is not affected by other complex parameters
|
|
(function irrelevantComplexParameterBinds([y] = [], z) {
|
|
assertEquals(undefined, x);
|
|
{
|
|
function x() {}
|
|
}
|
|
assertEquals('function', typeof x);
|
|
})();
|
|
|
|
// Hoisting is not affected by rest parameters
|
|
(function irrelevantRestParameterBinds(y, ...z) {
|
|
assertEquals(undefined, x);
|
|
{
|
|
function x() {}
|
|
}
|
|
assertEquals('function', typeof x);
|
|
})();
|
|
|
|
// Hoisting is not affected by complex rest parameters
|
|
(function irrelevantRestParameterBinds(y, ...[z]) {
|
|
assertEquals(undefined, x);
|
|
{
|
|
function x() {}
|
|
}
|
|
assertEquals('function', typeof x);
|
|
})();
|
|
|
|
|
|
// Test that shadowing function name is fine
|
|
{
|
|
let called = false;
|
|
(function shadowFunctionName() {
|
|
if (called) assertUnreachable();
|
|
called = true;
|
|
{
|
|
function shadowFunctionName() {
|
|
return 0;
|
|
}
|
|
assertEquals(0, shadowFunctionName());
|
|
}
|
|
assertEquals(0, shadowFunctionName());
|
|
})();
|
|
}
|
|
|
|
{
|
|
let called = false;
|
|
(function shadowFunctionNameWithComplexParameter(...r) {
|
|
if (called) assertUnreachable();
|
|
called = true;
|
|
{
|
|
function shadowFunctionNameWithComplexParameter() {
|
|
return 0;
|
|
}
|
|
assertEquals(0, shadowFunctionNameWithComplexParameter());
|
|
}
|
|
assertEquals(0, shadowFunctionNameWithComplexParameter());
|
|
})();
|
|
}
|
|
|
|
(function shadowOuterVariable() {
|
|
{
|
|
let f = 0;
|
|
(function () {
|
|
assertEquals(undefined, f);
|
|
{
|
|
assertEquals(1, f());
|
|
function f() { return 1; }
|
|
assertEquals(1, f());
|
|
}
|
|
assertEquals(1, f());
|
|
})();
|
|
assertEquals(0, f);
|
|
}
|
|
})();
|
|
|
|
(function notInDefaultScope() {
|
|
var y = 1;
|
|
(function innerNotInDefaultScope(x = y) {
|
|
assertEquals('undefined', typeof y);
|
|
{
|
|
function y() {}
|
|
}
|
|
assertEquals('function', typeof y);
|
|
assertEquals(1, x);
|
|
})();
|
|
})();
|
|
|
|
(function noHoistingThroughNestedLexical() {
|
|
{
|
|
let f = 2;
|
|
{
|
|
let y = 3;
|
|
function f() {
|
|
y = 2;
|
|
}
|
|
f();
|
|
assertEquals(2, y);
|
|
}
|
|
assertEquals(2, f);
|
|
}
|
|
assertThrows(()=>f, ReferenceError);
|
|
})();
|
|
|
|
// Only the first function is hoisted; the second is blocked by the first.
|
|
// Contrast overridingLocalFunction, in which the outer function declaration
|
|
// is not lexical and so the inner declaration is hoisted.
|
|
(function noHoistingThroughNestedFunctions() {
|
|
assertEquals(undefined, f); // Also checks that the var-binding exists
|
|
|
|
{
|
|
assertEquals(4, f());
|
|
|
|
function f() {
|
|
return 4;
|
|
}
|
|
|
|
{
|
|
assertEquals(5, f());
|
|
function f() {
|
|
return 5;
|
|
}
|
|
assertEquals(5, f());
|
|
}
|
|
|
|
assertEquals(4, f());
|
|
}
|
|
|
|
assertEquals(4, f());
|
|
})();
|
|
|
|
// B.3.5 interacts with B.3.3 to allow this.
|
|
(function hoistingThroughSimpleCatch() {
|
|
assertEquals(undefined, f);
|
|
|
|
try {
|
|
throw 0;
|
|
} catch (f) {
|
|
{
|
|
assertEquals(4, f());
|
|
|
|
function f() {
|
|
return 4;
|
|
}
|
|
|
|
assertEquals(4, f());
|
|
}
|
|
|
|
assertEquals(0, f);
|
|
}
|
|
|
|
assertEquals(4, f());
|
|
})();
|
|
|
|
(function noHoistingThroughComplexCatch() {
|
|
try {
|
|
throw 0;
|
|
} catch ({f}) {
|
|
{
|
|
assertEquals(4, f());
|
|
|
|
function f() {
|
|
return 4;
|
|
}
|
|
|
|
assertEquals(4, f());
|
|
}
|
|
}
|
|
|
|
assertThrows(()=>f, ReferenceError);
|
|
})();
|
|
|
|
(function hoistingThroughWith() {
|
|
with ({f: 0}) {
|
|
assertEquals(0, f);
|
|
|
|
{
|
|
assertEquals(4, f());
|
|
|
|
function f() {
|
|
return 4;
|
|
}
|
|
|
|
assertEquals(4, f());
|
|
}
|
|
|
|
assertEquals(0, f);
|
|
}
|
|
|
|
assertEquals(4, f());
|
|
})();
|
|
|
|
// Test that hoisting from blocks does happen in global scope
|
|
function globalHoisted() { return 0; }
|
|
{
|
|
function globalHoisted() { return 1; }
|
|
}
|
|
assertEquals(1, globalHoisted());
|
|
|
|
// Also happens when not previously defined
|
|
assertEquals(undefined, globalUndefinedHoisted);
|
|
{
|
|
function globalUndefinedHoisted() { return 1; }
|
|
}
|
|
assertEquals(1, globalUndefinedHoisted());
|
|
var globalUndefinedHoistedDescriptor =
|
|
Object.getOwnPropertyDescriptor(this, "globalUndefinedHoisted");
|
|
assertFalse(globalUndefinedHoistedDescriptor.configurable);
|
|
assertTrue(globalUndefinedHoistedDescriptor.writable);
|
|
assertTrue(globalUndefinedHoistedDescriptor.enumerable);
|
|
assertEquals(1, globalUndefinedHoistedDescriptor.value());
|
|
|
|
// When a function property is hoisted, it should be
|
|
// made enumerable.
|
|
// BUG(v8:4451)
|
|
Object.defineProperty(this, "globalNonEnumerable", {
|
|
value: false,
|
|
configurable: true,
|
|
writable: true,
|
|
enumerable: false
|
|
});
|
|
eval("{function globalNonEnumerable() { return 1; }}");
|
|
var globalNonEnumerableDescriptor
|
|
= Object.getOwnPropertyDescriptor(this, "globalNonEnumerable");
|
|
// BUG(v8:4451): Should be made non-configurable
|
|
assertTrue(globalNonEnumerableDescriptor.configurable);
|
|
assertTrue(globalNonEnumerableDescriptor.writable);
|
|
// BUG(v8:4451): Should be made enumerable
|
|
assertFalse(globalNonEnumerableDescriptor.enumerable);
|
|
assertEquals(1, globalNonEnumerableDescriptor.value());
|
|
|
|
// When a function property is hoisted, it should be overwritten and
|
|
// made writable and overwritten, even if the property was non-writable.
|
|
Object.defineProperty(this, "globalNonWritable", {
|
|
value: false,
|
|
configurable: true,
|
|
writable: false,
|
|
enumerable: true
|
|
});
|
|
eval("{function globalNonWritable() { return 1; }}");
|
|
var globalNonWritableDescriptor
|
|
= Object.getOwnPropertyDescriptor(this, "globalNonWritable");
|
|
// BUG(v8:4451): Should be made non-configurable
|
|
assertTrue(globalNonWritableDescriptor.configurable);
|
|
// BUG(v8:4451): Should be made writable
|
|
assertFalse(globalNonWritableDescriptor.writable);
|
|
assertFalse(globalNonEnumerableDescriptor.enumerable);
|
|
// BUG(v8:4451): Should be overwritten
|
|
assertEquals(false, globalNonWritableDescriptor.value);
|
|
|
|
// Test that hoisting from blocks does happen in an eval
|
|
eval(`
|
|
function evalHoisted() { return 0; }
|
|
{
|
|
function evalHoisted() { return 1; }
|
|
}
|
|
assertEquals(1, evalHoisted());
|
|
`);
|
|
|
|
// Test that hoisting from blocks happens from eval in a function
|
|
!function() {
|
|
eval(`
|
|
function evalInFunctionHoisted() { return 0; }
|
|
{
|
|
function evalInFunctionHoisted() { return 1; }
|
|
}
|
|
assertEquals(1, evalInFunctionHoisted());
|
|
`);
|
|
}();
|
|
|
|
// This test is incorrect BUG(v8:5168). The commented assertions are correct.
|
|
(function evalHoistingThroughSimpleCatch() {
|
|
try {
|
|
throw 0;
|
|
} catch (f) {
|
|
eval(`{ function f() {
|
|
return 4;
|
|
} }`);
|
|
|
|
// assertEquals(0, f);
|
|
assertEquals(4, f());
|
|
}
|
|
|
|
// assertEquals(4, f());
|
|
assertEquals(undefined, f);
|
|
})();
|
|
|
|
// This test is incorrect BUG(v8:5168). The commented assertions are correct.
|
|
(function evalHoistingThroughWith() {
|
|
with ({f: 0}) {
|
|
eval(`{ function f() {
|
|
return 4;
|
|
} }`);
|
|
|
|
// assertEquals(0, f);
|
|
assertEquals(4, f());
|
|
}
|
|
|
|
// assertEquals(4, f());
|
|
assertEquals(undefined, f);
|
|
})();
|
|
|
|
let dontHoistGlobal;
|
|
{ function dontHoistGlobal() {} }
|
|
assertEquals(undefined, dontHoistGlobal);
|
|
|
|
let dontHoistEval;
|
|
var throws = false;
|
|
try {
|
|
eval("{ function dontHoistEval() {} }");
|
|
} catch (e) {
|
|
throws = true;
|
|
}
|
|
assertFalse(throws);
|
|
|
|
// When the global object is frozen, silently don't hoist
|
|
// Currently this actually throws BUG(v8:4452)
|
|
Object.freeze(this);
|
|
{
|
|
let throws = false;
|
|
try {
|
|
eval('{ function hoistWhenFrozen() {} }');
|
|
} catch (e) {
|
|
throws = true;
|
|
}
|
|
assertFalse(this.hasOwnProperty("hoistWhenFrozen"));
|
|
assertThrows(() => hoistWhenFrozen, ReferenceError);
|
|
// Should be assertFalse BUG(v8:4452)
|
|
assertTrue(throws);
|
|
}
|