Source/JavaScriptCore/ChangeLog

 12016-04-06 Saam barati <sbarati@apple.com>
 2
 3 Initial implementation of annex b.3.3 behavior was incorrect
 4 https://bugs.webkit.org/show_bug.cgi?id=156276
 5
 6 Reviewed by Keith Miller.
 7
 8 I almost got annex B.3.3 correct in my first implementation.
 9 There is a subtlety here I got wrong. We always create a local binding for
 10 a function at the very beginning of execution of a block scope. So we
 11 hoist function declarations to their local binding within a given
 12 block scope. When we actually evaluate the function declaration statement
 13 itself, we must lookup the binding in the current scope, and bind the
 14 value to the binding in the "var" scope. We perform the following
 15 abstract operations when executing a function declaration statement.
 16
 17 f = lookupBindingInCurrentScope("func")
 18 store(varScope, "func", f)
 19
 20 I got this wrong by performing the store to the var binding at the beginning
 21 of the block scope instead of when we evaluate the function declaration statement.
 22 This behavior is observable. For example, a program could change the value
 23 of "func" before the actual function declaration statement executes.
 24 Consider the following two functions:
 25 ```
 26 function foo1() {
 27 // func === undefined
 28 {
 29 // typeof func === "function"
 30 function func() { } // Executing this statement binds the local "func" binding to the implicit "func" var binding.
 31 func = 20 // This sets the local "func" binding to 20.
 32 }
 33 // typeof func === "function"
 34 }
 35
 36 function foo2() {
 37 // func === undefined
 38 {
 39 // typeof func === "function"
 40 func = 20 // This sets the local "func" binding to 20.
 41 function func() { } // Executing this statement binds the local "func" binding to the implicit "func" var binding.
 42 }
 43 // func === 20
 44 }
 45 ```
 46
 47 * bytecompiler/BytecodeGenerator.cpp:
 48 (JSC::BytecodeGenerator::initializeBlockScopedFunctions):
 49 (JSC::BytecodeGenerator::hoistSloppyModeFunctionIfNecessary):
 50 * bytecompiler/BytecodeGenerator.h:
 51 (JSC::BytecodeGenerator::emitNodeForLeftHandSide):
 52 * bytecompiler/NodesCodegen.cpp:
 53 (JSC::FuncDeclNode::emitBytecode):
 54 * tests/stress/sloppy-mode-function-hoisting.js:
 55 (test.foo):
 56 (test):
 57 (test.):
 58 (test.bar):
 59 (test.switch.case.0):
 60 (test.capFoo1):
 61 (test.switch.capFoo2):
 62 (test.outer):
 63 (foo):
 64
1652016-04-06 Keith Miller <keith_miller@apple.com>
266
367 Unreviewed, uncomment accidentally commented line in test.
199143

Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp

@@void BytecodeGenerator::initializeBlockS
19341934 emitNewFunctionExpressionCommon(temp.get(), function);
19351935 bool isLexicallyScoped = true;
19361936 emitPutToScope(scope, variableForLocalEntry(name, entry, symbolTableIndex, isLexicallyScoped), temp.get(), DoNotThrowIfNotFound, Initialization);
 1937 }
 1938}
19371939
1938  if (iter->value.isSloppyModeHoistingCandidate() && m_scopeNode->hasSloppyModeHoistedFunction(name.impl())) {
1939  ASSERT(m_varScopeSymbolTableIndex);
1940  ASSERT(*m_varScopeSymbolTableIndex < m_symbolTableStack.size());
1941  SymbolTableStackEntry& varScope = m_symbolTableStack[*m_varScopeSymbolTableIndex];
1942  SymbolTable* varSymbolTable = varScope.m_symbolTable.get();
1943  RELEASE_ASSERT(varSymbolTable->scopeType() == SymbolTable::ScopeType::VarScope);
1944  SymbolTableEntry entry = varSymbolTable->get(name.impl());
1945  RELEASE_ASSERT(!entry.isNull());
1946  bool isLexicallyScoped = false;
1947  emitPutToScope(varScope.m_scope, variableForLocalEntry(name, entry, varScope.m_symbolTableConstantIndex, isLexicallyScoped), temp.get(), DoNotThrowIfNotFound, Initialization);
 1940void BytecodeGenerator::hoistSloppyModeFunctionIfNecessary(const Identifier& functionName)
 1941{
 1942 if (m_scopeNode->hasSloppyModeHoistedFunction(functionName.impl())) {
 1943 Variable currentFunctionVariable = variable(functionName);
 1944 RefPtr<RegisterID> currentValue;
 1945 if (RegisterID* local = currentFunctionVariable.local())
 1946 currentValue = local;
 1947 else {
 1948 RefPtr<RegisterID> scope = emitResolveScope(nullptr, currentFunctionVariable);
 1949 currentValue = emitGetFromScope(newTemporary(), scope.get(), currentFunctionVariable, DoNotThrowIfNotFound);
19481950 }
 1951
 1952 ASSERT(m_varScopeSymbolTableIndex);
 1953 ASSERT(*m_varScopeSymbolTableIndex < m_symbolTableStack.size());
 1954 SymbolTableStackEntry& varScope = m_symbolTableStack[*m_varScopeSymbolTableIndex];
 1955 SymbolTable* varSymbolTable = varScope.m_symbolTable.get();
 1956 ASSERT(varSymbolTable->scopeType() == SymbolTable::ScopeType::VarScope);
 1957 SymbolTableEntry entry = varSymbolTable->get(functionName.impl());
 1958 ASSERT(!entry.isNull());
 1959 bool isLexicallyScoped = false;
 1960 emitPutToScope(varScope.m_scope, variableForLocalEntry(functionName, entry, varScope.m_symbolTableConstantIndex, isLexicallyScoped), currentValue.get(), DoNotThrowIfNotFound, NotInitialization);
19491961 }
19501962}
19511963
199143

Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h

@@namespace JSC {
478478 return emitNode(n);
479479 }
480480
 481 void hoistSloppyModeFunctionIfNecessary(const Identifier& functionName);
 482
481483 private:
482484 void emitTypeProfilerExpressionInfo(const JSTextPosition& startDivot, const JSTextPosition& endDivot);
483485 public:
199143

Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp

@@void FunctionNode::emitBytecode(Bytecode
31583158
31593159// ------------------------------ FuncDeclNode ---------------------------------
31603160
3161 void FuncDeclNode::emitBytecode(BytecodeGenerator&, RegisterID*)
 3161void FuncDeclNode::emitBytecode(BytecodeGenerator& generator, RegisterID*)
31623162{
 3163 generator.hoistSloppyModeFunctionIfNecessary(metadata()->ident());
31633164}
31643165
31653166// ------------------------------ FuncExprNode ---------------------------------
199143

Source/JavaScriptCore/tests/stress/sloppy-mode-function-hoisting.js

@@test(function() {
222222test(function() {
223223 assert(foo === undefined);
224224 while (truthy()) {
 225 assert(foo() === 20);
225226 break;
226227
227228 function foo() { return 20; }
228229 }
 230 assert(foo === undefined);
 231});
 232
 233test(function() {
 234 assert(foo === undefined);
 235 while (truthy()) {
 236 assert(foo() === 20);
 237 function foo() { return 20; }
 238 break;
 239 }
229240 assert(foo() === 20);
230241});
231242

@@test(function() {
238249
239250 function foo() { return 20; }
240251 }
 252 assert(foo === undefined);
 253 assert(bar() === undefined);
 254});
 255
 256test(function() {
 257 function bar() { return foo; }
 258 assert(foo === undefined);
 259 assert(bar() === undefined);
 260 while (truthy()) {
 261 function foo() { return 20; }
 262 break;
 263 }
241264 assert(foo() === 20);
242265 assert(bar()() === 20);
243266});

@@test(function() {
280303 case 0:
281304 function foo() { return 20; }
282305 break;
 306 case 1:
 307 assert(foo() === 20);
 308 break;
 309 }
 310 assert(foo === undefined);
 311});
 312
 313test(function() {
 314 assert(foo === undefined);
 315 switch(1) {
 316 case 1:
 317 assert(foo() === 20);
 318 case 0:
 319 function foo() { return 20; }
 320 break;
283321 }
284322 assert(foo() === 20);
285323});

@@test(function() {
351389 break;
352390 }
353391
 392 assert(foo === undefined);
 393});
 394
 395test(function() {
 396 function capFoo1() { return foo; }
 397 assert(foo === undefined);
 398 assert(capFoo1() === undefined);
 399 switch(1) {
 400 case 0:
 401 function foo() { return bar; }
 402 function capFoo2() { return foo; }
 403 case 1:
 404 let bar = 20;
 405 assert(foo() === 20);
 406 assert(capFoo1() === undefined);
 407 assert(capFoo2() === foo);
 408 assert(capFoo2()() === 20);
 409 break;
 410 }
 411
 412 assert(foo === undefined);
 413});
 414
 415test(function() {
 416 assert(foo === undefined);
 417 switch(1) {
 418 case 1:
 419 let bar = 20;
 420 assert(foo() === 20);
 421 case 0:
 422 function foo() { return bar; }
 423 }
 424
354425 assert(foo() === 20);
355426});
356427

@@test(function() {
622693 assert(typeof z === "function");
623694});
624695
 696test(function() {
 697 function outer() { return f; }
 698 assert(outer() === undefined);
 699 {
 700 assert(outer() === undefined);
 701 assert(f() === 2);
 702 f = 100
 703 assert(outer() === undefined);
 704 function f() { return 1 }
 705 assert(outer() === 100);
 706 f = 200
 707 assert(outer() === 100); // 100
 708 function f() { return 2 }
 709 assert(outer() === 200);
 710 }
 711});
 712
625713for (let i = 0; i < 500; i++)
626714 assert(foo() === 25);
627715function foo() { return 20; }
199143

LayoutTests/js/function-declarations-in-switch-statement-expected.txt

@@PASS 20 is 20
33WARN: shouldBe() expects string arguments
44PASS 20 is 20
55WARN: shouldBe() expects string arguments
6 PASS 20 is 20
 6PASS -1 is -1
77PASS successfullyParsed is true
88
99TEST COMPLETE
199143

LayoutTests/js/script-tests/function-declarations-in-switch-statement.js

@@function t(n) {
2121
2222shouldBe(t(1), '20');
2323shouldBe(t(2), '20');
24 shouldBe(t(3), '20');
 24shouldBe(t(3), '-1');
199143