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

[ES1-7] return outside of function #935

Closed
Martii opened this issue Jan 25, 2016 · 29 comments
Closed

[ES1-7] return outside of function #935

Martii opened this issue Jan 25, 2016 · 29 comments

Comments

@Martii
Copy link
Contributor

Martii commented Jan 25, 2016

So we've got a few failures in minification already... this report is the first one, tested and confirmed.

A great way to circumvent by throwing a monkey wrench into UglifyJS2 (with the API) via Greasemonkey (GM) is to use the following script:

// ==UserScript==
// @name          RFC 2606§3 - Hello, World!
// @namespace     http://localhost.localdomain
// @description   Test minification with return statement
// @copyright     2007+, Marti Martz (https://openuserjs.org/users/Marti)
// @license       GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
// @license       (CC); http://creativecommons.org/licenses/by-nc-sa/3.0/
// @version       0.0.0
// @author        Marti Martz <somewhere@example.com> (https://openuserjs.org/users/Marti)

// @include   http://www.example.com/*
// @include   http://www.example.net/*
// @include   http://www.example.org/*
// ==/UserScript==

// ==OpenUserJS==
// @author Marti
// ==/OpenUserJS==


var condition = true;

if (condition) {
  return; // NOTE: Sabotage any minification with this valid code
} else {
  console.log('I am going to be doing something else now');
}

... which with our error trap will return this:

MINIFICATION WARNING (harmony):
  message: 'return' outside of function
  installName: Marti/RFC_2606§3_-_Hello,_World!.user.js
  line: 25 col: 8 pos: 859

With GM there may be a wrapped function call depending on version and in most browsers there is always an implied "caller". With some versions of recent Mozilla based browsers the caller is usually a restricted privileged application in which scoped caller will be forced to null or undefined for the scope of the script depending on injection.

Work around:

  1. This code can obviously be rewritten without the return statement and/or optionally placed in an IIFE (both the preferred Mozilla style and Chrome style included) however however it may be inconvenient for some and not fully backward compatible... and definitely a way to destroy the feature.
  2. Something else?

Suggestion_(s)_:

  1. Add a generator and/or compressor option to ignore this warning (not supposed to be an error) that we can set for looser strictness. (I'm not seeing this commit methodology referenced at these docs... Did I miss it?)
  2. Something else?

External refs:

Historical refs:

TIA

Cc: @fabiosantoscode and @avdg (mishoo will always get these as owner so omitting)

@avdg
Copy link
Contributor

avdg commented Jan 25, 2016

Are you using the harmony branch? If not, which version of uglifyJS are you running?

@Martii
Copy link
Contributor Author

Martii commented Jan 25, 2016

This is applicable for ES1 through ES7 as @mishoo wanted in the subject... doesn't currently matter if it's the harmony branch... e.g. happens in current release as well.

The CLI is referenced with the commit above but I think it was missed in the API version of this project. :\

@avdg
Copy link
Contributor

avdg commented Jan 25, 2016

Also, I just threw the code in the browser console. Chrome doesn't accept it either. So I'm not sure what to do since it seems to be a non-standard syntax.

Need to dig in the specs.

@Martii
Copy link
Contributor Author

Martii commented Jan 25, 2016

not sure what to do

The commit in the CLI is f36a1ea but seems to be absent in the API.

We are getting several dozen of these "errors" and GM handles them as "warnings" depending on if a function is available or not.

Chrome doesn't accept it either.

I'll try TamperMonkey with Chromium in a moment but I seem to recall it wraps too.

@avdg
Copy link
Contributor

avdg commented Jan 25, 2016

I see the spec does require return statement to occur in functions. So the best thing you can do is to wrap the code.

@avdg
Copy link
Contributor

avdg commented Jan 25, 2016

At least I hope it's not about a lot of code :-)

@Martii
Copy link
Contributor Author

Martii commented Jan 25, 2016

So the best thing you can do is to wrap the code.

We can't do that if it's a library script or something that is "export"ed into the sandbox. (also depends on .user.js engine too)

spec does require return statement to occur in functions

As I stated it's quite vague in the specs and got looser as ECMA-Script evolved... the implied function caller is always present in all browsers... the ES committee needs to strict'ify this a bit more in ES7... but until then it's valid syntax except for ES1.

@avdg
Copy link
Contributor

avdg commented Jan 25, 2016

As I stated it's quite vague in the specs and got looser as ECMA-Script evolved...

It's quite good defined in the grammar once a geek with above average understanding can read it. So there is no room for argument about that once the understanding is on the same level.

Note: es6 syntax grammer below

Statement[Yield, Return] :
    BlockStatement[?Yield, ?Return]
    VariableStatement[?Yield]
    EmptyStatement
    ExpressionStatement[?Yield]
    IfStatement[?Yield, ?Return]
    BreakableStatement[?Yield, ?Return]
    ContinueStatement[?Yield]
    BreakStatement[?Yield]
    [+Return] ReturnStatement[?Yield]
    WithStatement[?Yield, ?Return]
    LabelledStatement[?Yield, ?Return]
    ThrowStatement[?Yield]
    TryStatement[?Yield, ?Return]
    DebuggerStatement

ReturnStatement[Yield] :
    return ;
    return [no LineTerminator here] Expression[In, ?Yield] ; 

FunctionBody[Yield] :
    FunctionStatementList[?Yield]

FunctionStatementList[Yield] :
    StatementList[?Yield, Return]opt

@Martii
Copy link
Contributor Author

Martii commented Jan 25, 2016

I'll try TamperMonkey with Chromium in a moment but I seem to recall it wraps too.

Passes as valid code in Chromium Version 47.0.2526.106... which is where Chrome gets their code from.

... so ....

Chrome doesn't accept it either.

is invalid. (just stating a fact here so please don't get upset because I'm super literal)

@avdg
Copy link
Contributor

avdg commented Jan 25, 2016

Running chrome 47.0.2526.111 m (64-bit), pressing f12, pasting code, not working here.

@avdg
Copy link
Contributor

avdg commented Jan 25, 2016

Tampermonkey may wrap the code as a function, so that might make contextual difference (so that's chrome respecting the grammar on the parsing side because TamperMonkey probably injects code that way)

@Martii
Copy link
Contributor Author

Martii commented Jan 25, 2016

It's quite good defined in the grammar once a geek with above average understanding can read it. So there is no room for argument about that once the understanding is on the same level.

This portion could have been omitted though... I respect this project and you for what you've done but please, oh please, don't do passive-aggressive statements... anyhow enough of that off-topic reply to your response.

I spent the weekend drafting and rereading the specs and what @mishoo did was to allow the option in the CLI but it needs to be present in the API otherwise OUJS may have to drop any and all support of UgliflyJS2... which I am hoping isn't the case. I haven't even drafted up the next issue yet which is something you'll probably respond with similarly.

@avdg
Copy link
Contributor

avdg commented Jan 25, 2016

Note that I didn't take a decision in the UglifyJS situation yet, I'm just making sure we're on the same page.

@avdg
Copy link
Contributor

avdg commented Jan 25, 2016

But at the same time it wouldn't be interesting to output code with return statement in global-ish scope that ends up browsers saying that the code is invalid (though it's not likely we would encounter this phase by compressing the code anyway).

@rvanvelzen
Copy link
Collaborator

So, this is possible with --bare-returns on CLI, but not via the API. It's a trivial change, so you're free to send a PR.

@Martii
Copy link
Contributor Author

Martii commented Jan 26, 2016

@rvanvelzen

It's a trivial change, so you're free to send a PR.

Thanks.... Perhaps for a seasoned collaborator though... but as I pointed out to one of our users I'm just barely entering into this package directly... so a PR would be bound to be flawed or hit the wrong chord with someone since I don't fully have "the larger picture" of the logic of the code itself and the flow of things here just yet. There are some advanced items I haven't even tested yet but I did go looking for them. When a new collaborator comes into the fold there is bound to be some learning curves.

That being said... I'll see what I can do for an option when time permits outside of OUJS. I don't have the luxury of a single component project to maintain... I have 3 major duties alone just for OUJS production and development of OUJS so that makes more. The harmony branch is where this is most needed for the experimental test of UglifyJS2 and it will probably be there that I concentrate ... since there is release vs harmony. Cc: @fabiosantoscode

@rvanvelzen
Copy link
Collaborator

so a PR would be bound to be flawed or hit the wrong chord with someone

I doubt that, since it's all pretty straightforwardly implemented in the cli right now.

There's 2 relevant locations:

... But maybe I'm reading this all completely wrong, and those files point you to exactly what you need?

@Martii
Copy link
Contributor Author

Martii commented Jan 26, 2016

straightforwardly implemented in the cli right now.

The CLI isn't organized in the same representational logic as the API though... which is why I mentioned both API Objects above under suggestions e.g. I don't know if this would be a generator option, compressor option, or at the "top-level" where fromString resides... or another object that is specific to parseing... this is the part of the flow I haven't fully figured out yet with the "shortcut" of minify... being only a few weeks into this project directly.

@Martii
Copy link
Contributor Author

Martii commented Feb 6, 2016

Some scribblings (analysis notes that may change) out loud for this.

Taken from the README.md and also code comments e.g. a consolidation...

The current sequence is

  1. parse (this is where the option would be needed to be mapped to)
  2. compress
  3. mangle properties
  4. mangle
  5. generate output code.

vs minify() which has currently:

exports.minify = function(files, options) {
    options = UglifyJS.defaults(options, {
        spidermonkey     : false,
        outSourceMap     : null,
        sourceRoot       : null,
        inSourceMap      : null,
        fromString       : false,
        warnings         : false,
        mangle           : {},    // This is related to item 4 above
        mangleProperties : false, // This is related to item 3 above
        nameCache        : null,
        output           : null,  // This is related to item 5 above
        compress         : {}     // This is related to item 2 above
    });

So my guess is that parse would need to be created to minify's options sub-property... keeping in line with prior commits, logic structure e.g. output, compress, mangle, and mangleProperties property names.

This tells me to "match" as close as possible to the current project maintainers:

  • CLI implemented at 601780a/bin/uglifyjs#L356 and 601780a/lib/parse.js#L837
  • Never heard of "acorn"... looks like that is absent from the API too... or just not relevant
  • Code wise to use interCaps (camel-casing) for property names at the minify() level... other places it is underscored e.g. _
  • White-space seems somewhat ignored as ){ and ) { are present nearby... STYLEGUIDE stuff
  • Create parse there and pass/map those around 601780a/tools/node.js#L63-L65
  • Watch for false values on property names e.g. keep that in mind if "disabling" of a feature is needed or passing something like a Function Expression... this particular option appears to be boolean for the CLI switch... default to false but generally is undefined when possible
  • spidermonkey (which is bit-rotting btw) passes no arguments at 601780a/tools/node.js#L54-L55
  • Modify the README.md to include "Userscripts" e.g. ~"Useful when minifying CommonJS modules and Userscripts that may be anonymous function wrapped (IIFE) by the .user.js engine caller." ...typically 80 column limit on this but not always adhered to
  • Possibly add to the README.md in the section header for this option since it's missing too (NOTE: strict doesn't appear to be in the referenced code ... expression doesn't appear in the README.md... these are beyond the scope of this issue though)
  • Currently ~41 open pull-requests... some dating to 2012 _(yikes)_

@Martii
Copy link
Contributor Author

Martii commented Feb 6, 2016

@rvanvelzen Cc: @mishoo , @fabiosantoscode and anyone else who cares to chime in
I think I have enough analysis data above to do this (at least try) but...

This is going to get way confusing here on how to do this... OUJS has a fork of upstream here... and as a rule of thumb, so far, if I fork our upstream personally (which is preferred)... I'd want to incorporate that into OUJS's fork so we could use it right away... in which case upstream here would never see the PR I think... then I'd have to merge it into the harmony branch and the harmony-named branch.

For upstream here a git remote would need to be added I think to the OUJS fork to grab the PR there... but never done it this complicated before.

Any ideas on making this simpler with the above requirements?

@fabiosantoscode
Copy link
Contributor

Returning outside of a function is really not per the spec. It's only possible in node (since it wraps its modules in a function) but imho it's a bug.

But!

I have been known to use this to early-return from browser-only node modules, for example, and it turns out to be pretty useful. Tbh uglifyjs shouldn't concern itself with imposing the restriction of a return being inside a function, and although I haven't checked, the change is probably just the removal of an assertion.

Whether to do this, however, is certainly not my decision, and neither is defining what uglifyjs should do in the face of invalid constructs.

However a workaround exists, which is to wrap the module in a function declaration before minification and remove it afterwards.

@Martii
Copy link
Contributor Author

Martii commented Feb 6, 2016

early-return from browser-only node modules

Yah that's what I've seen it used with... I usually IIFE my Userscripts going way back and never see this... but asking everyone on the planet to do this from our standpoint won't travel very far... especially with library scripts that are pre export ;)

I've monkey hacked it into our node_modules folder harmony branch (a massive 2 new lines total for change besides the README.md) and using the data analysis from above it works!:

// ==UserScript==
// @name          RFC 2606§3 - Hello, World!
// @namespace     http://localhost.localdomain
// @description   Test minification with return statement
// @copyright     2007+, Marti Martz (https://openuserjs.org/users/Marti)
// @license       GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
// @license       (CC); http://creativecommons.org/licenses/by-nc-sa/3.0/
// @version       0.0.0
// @author        Marti Martz <somewhere@example.com> (https://openuserjs.org/users/Marti)
// @include   http://www.example.com/*
// @include   http://www.example.net/*
// @include   http://www.example.org/*
// ==/UserScript==
// ==OpenUserJS==
// @author Marti
// ==/OpenUserJS==
var condition=!0;if(condition)return;console.log("I am going to be doing something else now");

... so until GM/TamperMonkey/and the plethora of other extensions/add-ons that do Userscripts don't wrap as the caller we need it I believe... plus this abstracts it enough when it is "deprecated" or "strictly defined" in ES7+ ... the loose nature is still around... plus it's already in the CLI... just needs mapping to the API for usage. :)


@fabiosantoscode

However a workaround exists, which is to wrap the module in a function declaration before minification and remove it afterwards.

There are too many possibilities on how it would be wrapped e.g. unpredictable... sometimes the shebang, which is added, may or may not be added to the function and the transformation isn't static from what I've seen... already thought of that too that one weekend.

Tbh uglifyjs shouldn't concern itself with imposing the restriction of a return being inside a function

Agree ... this should be a pass through imho... mapping the existing bare_returns from the CLI to the API does this.

@Martii
Copy link
Contributor Author

Martii commented Feb 6, 2016

So there's the code reference... time to see what chords this twinges. :)

@Martii
Copy link
Contributor Author

Martii commented Feb 6, 2016

Cleaned up the commits a bit more in a new PR... same code though with some README.md enhancements... a few hours of RnR help for clarity.

As I forewarned fabiosantoscode I'm a bit noisy, this time due to learning curve and a few tired mistakes... so apologies again and hopefully I have this complex forking system down a bit better. :)

@Martii
Copy link
Contributor Author

Martii commented Feb 8, 2016

@rvanvelzen
Thank you!!! :)

@fabiosantoscode
Now that cdba43c is merged... to close this issue would you be willing to do some updates in your harmony branch? or should I just go with our harmony-named fork on OUJS and do that until you are ready to match a release?

@fabiosantoscode
Copy link
Contributor

@Martii the harmony branch on this repository is not under my control, it's usually the maintainers who dilligently merge master into it once in a while <3

@Martii
Copy link
Contributor Author

Martii commented Feb 8, 2016

@fabiosantoscode
Okeee... I'll probably just map the commit into our harmony-named branch (or maybe another branch to keep it as clean for merge conflicts) then...will ease up the bunches of stderr messages we get and I get to read. ;) :) Thanks.

@avdg
Copy link
Contributor

avdg commented May 25, 2016

Can this issue be closed?

@Martii
Copy link
Contributor Author

Martii commented May 25, 2016

Merged into harmony with f94497d and master in cdba43c ... final PR at #962

Sorry about that.

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

No branches or pull requests

4 participants