d54ffadfda
In edge cases such as the following, sloppy-mode block-scoped function hoisting is expected to occur: eval(` with({a: 1}) { function a() {} } `) In this case, there should be the equivalent of a var declaration outside of the eval, which gets set to the value of the local function a when the body of the with is executed. Previously, the way that var declarations are hoisted out of eval meant that the assignment to that var was an ordinary DYNAMIC_GLOBAL assignment. However, such a lookup mode meant that the object in the with scope received the assignment! This patch fixes that error by marking the assignments produced by the sloppy mode block scoped function hoisting desugaring so as to generate a different runtime call which skips with scopes. Bug: chromium:720247, v8:5135 Change-Id: Ie36322ddc9ca848bf680163e8c016f50d4597748 Reviewed-on: https://chromium-review.googlesource.com/529230 Commit-Queue: Daniel Ehrenberg <littledan@chromium.org> Reviewed-by: Michael Starzinger <mstarzinger@chromium.org> Reviewed-by: Ross McIlroy <rmcilroy@chromium.org> Reviewed-by: Adam Klein <adamk@chromium.org> Cr-Commit-Position: refs/heads/master@{#46116}
672 lines
13 KiB
JavaScript
672 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());
|
|
}
|
|
|
|
// 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());
|
|
})();
|
|
|
|
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);
|
|
}
|