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

incorrectly minifying q 8.12 library #153

Closed
CrisFavero opened this issue Mar 18, 2013 · 14 comments
Closed

incorrectly minifying q 8.12 library #153

CrisFavero opened this issue Mar 18, 2013 · 14 comments

Comments

@CrisFavero
Copy link

the following file https://github.com/kriskowal/q/blob/76afd7b7abaad30cbfee394c72227d9dc06154e6/q.js when run through uglify generates a

    function t(n) {
        return "[object StopIteration]" === he(n) || n instanceof se;
    }

which is the equivalent of

function isStopIteration(exception) {
    return (
        object_toString(exception) === "[object StopIteration]" ||
        exception instanceof QReturnValue
    );
}

which is not the correct as the line called by this

if (n = t(n), v(n)) 

should instead evaluate to code produced for the function valueOf;

@mishoo
Copy link
Owner

mishoo commented Mar 19, 2013

I'm sorry, I don't understand this report. Can you be more specific as to what exactly do you find broken?

@SLaks
Copy link

SLaks commented Mar 19, 2013

Lines 685 - 687 of the original code are:

    object = valueOf(object);
    // assimilate thenables, CommonJS/Promises/A
    if (isPromiseAlike(object)) 

Uglify transforms that to:

if (n = t(n), v(n)) {

The problem is that the call to valueOf() becomes a call to the wrong function (t()):

    function t(n) {
        return "[object StopIteration]" === he(n) || n instanceof se;
    }

t() is actually the mangled name of isStopIteration():

function isStopIteration(exception) {
    return (
        object_toString(exception) === "[object StopIteration]" ||
        exception instanceof QReturnValue
    );
}

It should have emitted a call to l() (which is the actual mangled name of valueOf():

    function l(n) {
        return d(n) ? n.valueOf() : n;
    }

Unchecking the Mangle checkbox in the online demo causes it to emit correct code.

@mishoo
Copy link
Owner

mishoo commented Mar 19, 2013

If you use the following to compress:

u2 q.js -m -o mq.js -b --comments=all -c

(I used --comments to keep comments in the output, and passed -b to make it easier for me to follow the minified/compressed code).

Here's what happens. In the output (mq.js) at line 242, code looks like this:

        }, void 0, function t() {
            return n;
        });

That's the t that your if should refer to. It corresponds to line 738 in the original code:

    }, void 0, function valueOf() {
        return object;
    });

So minification is correct. The original code is wrong, of course, because that's not a function declaration—it's a function expression instead, and so the valueOf name should simply not be visible in that if expression (in compliant engines at least; it will work in IE, but that's because IE is buggy.)

@mishoo mishoo closed this as completed Mar 19, 2013
@SLaks
Copy link

SLaks commented Mar 19, 2013

You're misunderstanding the original code.

The valueOf() function called by line 685 is not the function expression on line 738; it's the function declaration on line 563.

https://github.com/kriskowal/q/blob/76afd7b7abaad30cbfee394c72227d9dc06154e6/q.js#L563-L568

It looks like Uglify is incorrectly making the valueOf() from the function expression visible in its parent scope.

@mishoo
Copy link
Owner

mishoo commented Mar 19, 2013

Ah, I understand.. And you are right — but UglifyJS behaves this way to be compliant with this quirk in Internet Explorer. AFAIK, in IE the function expression on line 738 will take precedence in that scope and override the outer valueOf function.

A simple solution would be for you to rename the function at line 738.

@SLaks
Copy link

SLaks commented Mar 19, 2013

I was wondering if that was the case, but unchecking IE-proof output doesn't fix it.

I didn't write the original library, and I don't know if it works in IEs that have that bug.

@mishoo
Copy link
Owner

mishoo commented Mar 19, 2013

Or (sorry, I forgot, this was recently added) — another solution would be to pass --screw-ie. Seriously. ;-)

@mishoo
Copy link
Owner

mishoo commented Mar 19, 2013

Ah, IE-proof output is a code generator option that relates to something else. The --screw-ie that I'm talking about is not exposed in the online demo yet, you'd have to use the command-line tool.

@SLaks
Copy link

SLaks commented Mar 19, 2013

For anyone else having this issue, just use Q v0.9+, which doesn't have the problem in the first place

mishoo added a commit that referenced this issue Mar 22, 2013
Previously:

    Without `--screw-ie`, UglifyJS would always leak names of function
    expressions into the containing scope, as if they were function
    declarations.  That was to emulate IE<9 behavior.  Code relying on this
    IE bug would continue to work properly after mangling, although it would
    only work in IE (since other engines don't share the bug).  Sometimes
    this broke legitimage code (see #153 and #155).

    With `--screw-ie` the names would not be leaked into the current scope,
    working properly in legit cases; but still it broke legit code when
    running in IE<9 (see #24).

Currently:

    Regardless of the `--screw-ie` setting, the names will not be leaked.
    Code relying on the IE bug will not work properly after mangling.
    <evil laughter here>

    Without `--screw-ie`: a hack has been added to the mangler to avoid
    using the same name for a function expression and some other variable in
    the same scope.  This keeps legit code working, at the (negligible,
    indeed) cost of one more identifier.

    With `--screw-ie` you allow the mangler to name function expressions
    with the same identifier as another variable in scope.  After mangling
    code might break in IE<9.

Oh man, the commit message is longer than the patch.

Fix #153, #155
@lefnire
Copy link

lefnire commented May 9, 2013

changes in 2.3.2 also caused this issue for me (SLak's link HabitRPG/habitica#920). pinning to 2.3.1 fixed

@SLaks
Copy link

SLaks commented May 9, 2013

@lefnire's issue is caused by transforming (function(){})() to !function(){}(), which Racer didn't expect.

Fixed by derbyjs/racer#130

@mishoo
Copy link
Owner

mishoo commented May 9, 2013

@SLaks heh, sorry about that. The transformation is valid, so that's why it's doing it unconditionally.

BTW, I see in a comment there “Uglify can't parse a naked function. Executing it allows Uglify to parse it properly”. It's not a parser issue, it's in the JS spec. (“naked” functions must have a name, otherwise it's a syntax error; I suspect that is the problem).

@SLaks
Copy link

SLaks commented May 9, 2013

Uglify is in the right here; you've saved one character.

The fundamental problem there is that Racer wants to uglify an expression (function expression), whereas Uglify is designed to uglify statements (and function declaration statements must have names).

Should Uglify have a function to uglify expressions rather than statements?

@mishoo
Copy link
Owner

mishoo commented May 9, 2013

Well, UglifyJS is designed to uglify programs, and a JS program is a sequence of statements.

But yeah, we could have an option to parse expressions.

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