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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

track returned state in function body #14357

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open

track returned state in function body #14357

wants to merge 17 commits into from

Conversation

vankop
Copy link
Member

@vankop vankop commented Sep 28, 2021

What kind of change does this PR introduce?

feature

closes #14347

Did you add tests for your changes?

yes

Does this PR introduce a breaking change?

no

What needs to be documented once your changes are merged?

nothing

Notes regarding feature design

Added 2 properties to parser current scope (parser.scope)

  • terminated marks that further parsing of nearest BlockStatement should be terminated. Has 3 states 'return'|'throw'|undefined, where undefined = false, 'return'|'throw' types of termination
    returned is inherited from child BlockStatement (or terminate node in case of if (true) return;) to nearest parent BlockStatement if executedPath=true
  • executedPath marks that current statement will be executed if nearest BlockStatement will be reached, e.g.
function a() {  // <- executedPath=true
  return; // <- terminated=return
  // should terminate parsing of current BlockStatement
}
function a() {  // <- executedPath=true
  if (rand()) { // <- executedPath=false marks 'unknown' state
     return 1; // terminated=return
  }  // <- terminated=undefined we do not know will this be executed or not (child block executedPath=false)
  
  if (true) {  // <- executedPath=true
       throw 2;  // <- terminated=throw
  }
   // should terminate parsing of current BlockStatement
   require('c')
}
function a() {  // <- executedPath=true
  for (;;) {  // <- executedPath=false for all loops
     require('a') // should be required
     if (true) {  // <- executedPath=true
         return;  // <- terminated=return
     }
     // should terminate parsing of current BlockStatement
     require('c')
  } // <- terminated=undefined
  
  try { // <- executedPath=true same executedPath for each inner block
     throw 2; // <- terminated=throw
     // should not be parsed
     require()
  } catch(e) {  // executedPath=false for every catch
   
  } // terminated=undefined throw in try
  // continue parsing..
}

complex example

function a() {  // <- executedPath=true
  if (rand()) {  // <- executedPath=false
    if (true) {  // <- executedPath=true
        return;  // <- terminated=return
    }
    // should terminate parsing of current BlockStatement
    require('c')
  }  // <- executedPath=true, terminated=undefined
  
  if (true) {  // <- executedPath=true
       throw 2;  // <- terminated=throw
  }  // <- executedPath=true, terminated=throw

   // should terminate parsing of current BlockStatement
   require('c')
}

and one more 馃敒

function a(name) { // executedPath=true
switch (name) { // executedPath=false for each switch
  case "a": { // executedPath=false from parent
     if (true) return; // executedPath=true, terminated=return
     // skip parsing current block
     require('a')
     return 1;
  } // terminated=undefined since `case: a` block is executedPath=false
  case "b":
     if (true) return; // executedPath=true, terminated=return
     // skip parsing current switch case, but not current block 鈿狅笍
     // we should not mark current block as returned=true
     require('a');
     return 2;
  case "c":
    // should be parsed
     return require('c');
  case "d":
    // // should be parsed
     return require('c');
  default:
     throw new Error("Unexcepted test data");
}
}

Notes regarding feature implementation

  • right now parser.hooks.terminate looks kind of useless, not sure about it..

TODO
drop skipped code. Any ideas how to do it better? we should do this from terminated!=undefined blocks till terminated=undefined blocks

@webpack-bot
Copy link
Contributor

For maintainers only:

  • This needs to be documented (issue in webpack/webpack.js.org will be filed when merged)
  • This needs to be backported to webpack 4 (issue will be created when merged)

@webpack-bot
Copy link
Contributor

Thank you for your pull request! The most important CI builds succeeded, we鈥檒l review the pull request soon.

fix case when in try we throw and terminated=undefined for current scope
@sokra sokra self-assigned this Jan 31, 2022
@alexander-akait
Copy link
Member

@vankop Can you rebase? Thank you

# Conflicts:
#	lib/ConstPlugin.js
@TheLarkInn
Copy link
Member

TheLarkInn commented Jun 5, 2023

  • Add test case for hoisted function after return
  • Add test case for dynamic import after return
  • Change test case to use require("fail") instead of importing in existing module (so it actually fails compiling when parsing that part)

@@ -1799,6 +1805,7 @@ class JavascriptParser extends Parser {
for (let index = 0, len = statements.length; index < len; index++) {
const statement = statements[index];
this.walkStatement(statement);
if (this.scope.terminated) break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this solution, I'm only afraid of one thing, that some plugins could rely on it and rewrite something, and now we just will not handle them at all

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but this code is unreachable.. so I dont see a reason to waste cpu =)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree with you, I'm just worried that someone could use similar logic, but we can merge and look at feedback, It's not difficult to do a revert

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Ready for Merge
Development

Successfully merging this pull request may close these issues.

Dead code eliminator for constant expression doesn't handle "return early" if statements
5 participants