v8/test/mjsunit/es6/block-sloppy-function.js
Daniel Ehrenberg d54ffadfda [scopes] Fix sloppy-mode block-scoped function hoisting edge case
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}
2017-06-22 08:18:55 +00:00

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);
}