f2303d9a34
That makes the declaration in sync with how dynamic references are resolved, avoiding duplicate variable creation in the likely case that the variable is also referenced within the eval. Bug: v8:5112, v8:5135, v8:8693 Change-Id: I0c55495f573fe8b5076b1627c139ff72d1adda74 Also-by: leszeks@chromium.org Reviewed-on: https://chromium-review.googlesource.com/c/1408890 Commit-Queue: Toon Verwaest <verwaest@chromium.org> Reviewed-by: Leszek Swirski <leszeks@chromium.org> Cr-Commit-Position: refs/heads/master@{#58850}
669 lines
13 KiB
JavaScript
669 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 shadowingLetDoesntBindGenerator() {
|
|
let f = function *f() {
|
|
while(true) {
|
|
yield 1;
|
|
}
|
|
};
|
|
assertEquals(1, f().next().value);
|
|
{
|
|
function *f() {
|
|
while(true) {
|
|
yield 2;
|
|
}
|
|
}
|
|
assertEquals(2, f().next().value);
|
|
}
|
|
assertEquals(1, f().next().value);
|
|
})();
|
|
|
|
(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());
|
|
`);
|
|
}();
|
|
|
|
(function evalHoistingThroughSimpleCatch() {
|
|
try {
|
|
throw 0;
|
|
} catch (f) {
|
|
eval(`{ function f() {
|
|
return 4;
|
|
} }`);
|
|
|
|
assertEquals(0, f);
|
|
}
|
|
|
|
assertEquals(4, f());
|
|
})();
|
|
|
|
(function evalHoistingThroughWith() {
|
|
with ({f: 0}) {
|
|
eval(`{ function f() {
|
|
return 4;
|
|
} }`);
|
|
|
|
assertEquals(0, f);
|
|
}
|
|
|
|
assertEquals(4, 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);
|
|
}
|