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