Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed problems with hoisting #1806

Merged
merged 2 commits into from
Feb 4, 2025
Merged

Fixed problems with hoisting #1806

merged 2 commits into from
Feb 4, 2025

Conversation

andreabergia
Copy link
Contributor

This PR fixes some problems with function hoisting in nested scopes. Consider this:

function outer() {
  inner();

  function inner() {}
  do {
    inBlock();
    function inBlock() {} 
  } while (false);

  var expr = function() {}
}

The hoisting rules say that this is sorta equivalent to:

function outer() {
  // Hoisting declaraitons
  var inner;
  var inBlock;
  var expr;
  inner = function inner() {}

  inner();

  do {
    // Hoisting
    inBlock = function inBlock() {} 

    inBlock();
  } while (false);

  expr = function() {}
}

Rhino correctly handles hoisting at the top level of the function (i.e. for inner), but it does not handle it correctly for nested blocks (i.e. for inBlock). So in the example above, the following exception gets thrown:

TypeError: inBlock is not a function, it is undefined

This PR fixes the problem. The trick is that, whenever the IR for a block is generated, nested functions are hoisted on top of it.

@p-bakker
Copy link
Collaborator

Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function#block-level_function_declaration, I don't think any assumptions are to be made as to whether functions declarations within blocks are hoisted or not, at least in non-strict mode.

When in Strict mode however, the function should be hoisted, but only to the top of it's containing block, so not the outer function

Also see related PR/discussion #970

@andreabergia
Copy link
Contributor Author

andreabergia commented Jan 30, 2025

Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function#block-level_function_declaration, I don't think any assumptions are to be made as to whether functions declarations within blocks are hoisted or not, at least in non-strict mode.

When in Strict mode however, the function should be hoisted, but only to the top of it's containing block, so not the outer function

Also see related PR/discussion #970

We discovered this because of a problem reported by one of our customers. What we implemented is coherent with the other engines that we have tested (v8, jsc, spidermonkey, quickjs) from what we can tell.

Functions declared inside blocks are not hoisted to the top of the function; the equivalent var declaration is, though. But, the initialization is done only in the block.

Example:

if (typeof 'print' === 'undefined') {
  print = console.log.bind(console);
}

(function test() {
  print(typeof inner);
  if (true) {
    print(typeof inner);
    function inner() {}
  }
})();

This prints: undefined function in v8, spidermonkey, jsc.
In rhino before this patch it prints undefined undefined, but it is coherent with other engines after this PR.

@gbrail
Copy link
Collaborator

gbrail commented Jan 31, 2025

I'm all for doing it. I wonder why there aren't any test262 tests that fail because of this behavior, though. Are there other tests we can pull from the V8 test suite (We have also used the "mjsunit" tests from the V8 repo somewhere) so that we can make sure that we're not off on our own here? Otherwise I think this is a good change!

@gbrail
Copy link
Collaborator

gbrail commented Feb 4, 2025

This looks fine to me. Thanks for doing it!

@gbrail gbrail merged commit 177b047 into mozilla:master Feb 4, 2025
3 checks passed
@andreabergia andreabergia deleted the scratch/s/hoisting branch February 5, 2025 08:05
@andreabergia
Copy link
Contributor Author

I'm all for doing it. I wonder why there aren't any test262 tests that fail because of this behavior, though. Are there other tests we can pull from the V8 test suite (We have also used the "mjsunit" tests from the V8 repo somewhere) so that we can make sure that we're not off on our own here? Otherwise I think this is a good change!

Sorry, didn't get around to check that.
I'm a bit surprised too that there were no test262 cases, honestly.

@rbri
Copy link
Collaborator

rbri commented Feb 5, 2025

Sorry, didn't get around to check that.
I'm a bit surprised too that there were no test262 cases, honestly.

Same here but at least there was a test in HtmlUnit/core-js and also a fix for that. Have removed our fix and all the tests are still passing. Thanks @andreabergia for all your work (hope for more)

HtmlUnit@e725bba
HtmlUnit/htmlunit-core-js@f6df509

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants