v8/test/mjsunit/harmony/block-sloppy-function.js
littledan 76da493315 Extend Annex B 3.3 sloppy-mode block-scoped hoisting to scripts, eval
The ES2015 spec is missing an extension of sloppy-mode block-scoped function
behavior to the global scope in scripts, as well as to eval. This patch
brings that hoisting to those two areas. The behavior is not perfectly
spec-compliant since properties created on the global scope should be
set as enumerable even if they are non-enumerable previously, but the
attributes will not be modified if the property already exists under
this patch.

BUG=v8:4441
LOG=Y
R=adamk
TEST=reddit comment functionality seems to be fixed

Review URL: https://codereview.chromium.org/1376623002

Cr-Commit-Position: refs/heads/master@{#31037}
2015-09-30 23:48:34 +00:00

303 lines
6.9 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.
// Flags: --no-legacy-const --harmony-sloppy --harmony-sloppy-let
// Flags: --harmony-sloppy-function --harmony-destructuring
// Flags: --harmony-rest-parameters
// 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 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());
})();
// 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);
// Shadow function parameter
(function shadowParameter(x) {
assertEquals(1, x);
{
function x() {}
}
assertEquals('function', typeof x);
})(1);
// Shadow function parameter
(function shadowDefaultParameter(x = 0) {
assertEquals(1, x);
{
function x() {}
}
// TODO(littledan): Once destructured parameters are no longer
// let-bound, enable this assertion. This is the core of the test.
// assertEquals('function', typeof x);
})(1);
(function shadowRestParameter(...x) {
assertArrayEquals([1], x);
{
function x() {}
}
// TODO(littledan): Once destructured parameters are no longer
// let-bound, enable this assertion. This is the core of the test.
// assertEquals('function', typeof x);
})(1);
assertThrows(function notInDefaultScope(x = y) {
{
function y() {}
}
assertEquals('function', typeof y);
assertEquals(x, undefined);
}, ReferenceError);
// 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());
`);
}();
let dontHoistGlobal;
{ function dontHoistGlobal() {} }
assertEquals(undefined, dontHoistGlobal);
let dontHoistEval;
// BUG(v8:) This shouldn't hoist and shouldn't throw
var throws = false;
try {
eval("{ function dontHoistEval() {} }");
} catch (e) {
throws = true;
}
assertTrue(throws);
// When the global object is frozen, silently don't hoist
// Currently this actually throws BUG(v8:4452)
Object.freeze(this);
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);