-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Bug: Uglified babel polyfill with reduce_vars option causes string.split to misbehave in chrome 51 #1964
Comments
In particular the culprit seems to be enabling |
The example above is ES6 code using I cannot reproduce the issue using ES5 code:
It would be helpful if someone would post a standalone example of breaking ES5 code with no dependencies other than |
@kzc sorry i think my original comment was a little misleading. the expected and actual outputs are not being run through uglify. they are just run in the console directly. uglify is run on the app itself (after running through babel and webpack to convert to es5). loading the app causes the global String.split prototype method to misbehave once loading is done (so in the console or if the app uses string.split) |
I get that the ES6 is transpiled to ES5. It's just that we don't have the time to learn third party tools and foreign applications for a bug report. If you can generate the ES5 code without uglify and post it in a gist that would be helpful. |
oh sorry yea here's the gist. https://gist.github.com/bdwain/aa93e842a2fd4d23f0e710eee68ddd98 (edited to use a new gist without react or anything in it) |
for comparison, this gist works https://gist.github.com/bdwain/d252d19b2ea933abdcc281f6664b8c07 the only difference is that i removed the import of babel-polyfill (edited to use a new gist without react or anything in it) |
@kzc i edited the examples and gists to remove react from them because it was irrelevant. the full source code is now just
|
I guess it would be nice to trim it down to specific code in Babel polyfill (since that's just JS too). |
i updated https://github.com/bdwain/uglify-bug to remove create-react-app and webpack from the example. I was able to reproduce it just by running uglify on the babel polyfill and including that in index.html. |
CLI repro:
Create this
Regression introduced in 16cd5d5 |
@alexlamsl Just a hunch - can you disable replacement of all RegExp literals in |
@kzc thanks for the CLI repo - I was just scanning through the diff between var lt = "<", gt = ">";
... lt + "script" + gt ... I wonder if that defeats some mechanism 😅 (Read: nothing confirmed yet, about to find out...) |
I'll give that a go as well in a moment 😉 |
@kzc would you mind pointing out what I did wrong? 😅 > type test.js
require("babel-polyfill");
var str = "fooBar";
console.log( str.split(/(?=[A-Z])/) );
> nvs use node
PATH = node\7.10.0\x64
> npm install babel-polyfill@6.23.0 browserify@14.3.0 uglify-js@3.0.7
> node_modules\.bin\browserify test.js > bundle.js
> type bundle.js | node
[ 'foo', 'Bar' ]
> node_modules\.bin\uglifyjs bundle.js -c | node
[ 'foo', 'Bar' ]
> node_modules\.bin\uglifyjs bundle.js -c reduce_vars | node
[ 'foo', 'Bar' ]
> node_modules\.bin\uglifyjs bundle.js -c reduce_vars=true | node
[ 'foo', 'Bar' ]
> node_modules\.bin\uglifyjs bundle.js -c reduce_vars=false | node
[ 'foo', 'Bar' ] |
That should work. Try this instead:
Barring that, try it on Mac or Linux. Or bundle the program using |
Ugh ❗ > nvs use node/0.10
> node_modules\.bin\browserify test.js > bundle.js
> type bundle.js | node
[ 'foo', 'Bar' ]
> node_modules\.bin\uglifyjs bundle.js -c | node
[ 'foo', 'Bar' ] > nvs use node/4
> node_modules\.bin\browserify test.js > bundle.js
> type bundle.js | node
[ 'foo', 'Bar' ]
> node_modules\.bin\uglifyjs bundle.js -c | node
[ 'foo', 'Bar' ] > nvs use node/6
> node_modules\.bin\browserify test.js > bundle.js
> type bundle.js | node
[ 'foo', 'Bar' ]
> node_modules\.bin\uglifyjs bundle.js -c | node
[ 'fooBar' ] > nvs use node/7
> node_modules\.bin\browserify test.js > bundle.js
> type bundle.js | node
[ 'foo', 'Bar' ]
> node_modules\.bin\uglifyjs bundle.js -c | node
[ 'foo', 'Bar' ] ... so the issue is specific to Node.js 6 somehow? |
The bug is related to node 6:
|
Lordy! 🙈 I guess I can run through replacing bits of (I need to head out for a meeting now. I'll be back to analyse this later today if nobody beats me to it.) |
It makes no sense - the file
The polyfill code must have some obscure runtime assumption of some kind. |
Before we go further down the rabbit hole, @bdwain can you confirm if your original issue is run on Node.js 6, and that it works on Node.js 4 or 7? |
@alexlamsl i haven't run on 6. my examples fail for node 7.9. |
The version of node used to run uglify does not matter. What matters is the browser or node engine that runs the minified code. |
oh sorry. i've only run this in chrome 51. i haven't run it in node. i can try to set it up, but chrome 51 is the issue for me. every other version is fine. what's confusing to me is what in the browser could mess with the global string prototype, which is a native method. I checked when the bug happened, and the split method had not been replaced by some other function. |
Can you run the minified bundle in a non-Chrome browser (FF, Safari, IE11) and report your results? |
it worked in all 3 of those. though it also works in the latest version of chrome, so there may be bugs in older versions of those browsers as well. I know it works as far back as chrome 30 with no issues though, except for 51. |
also, for the record i'm on a mac (Sierra) though i actually first noticed this issue in android. it also happens in windows chrome 51 as well |
@bmeurer We would greatly appreciate your opinion on this matter: #1964 (comment) |
The usual use case of In this case I think we are hitting this: $ cat test.js
[
42,
NaN,
true,
null,
"string",
undefined,
/regexp/ig,
].forEach(function(value) {
try {
value.field = true;
if (value.field) {
console.log(value);
}
} catch (e) {}
});
$ node test.js
{ /regexp/gi field: true } |
@kzc do you think putting replacement of According to http://caniuse.com/usage-table Chrome 51 isn't quite as popular. It thinks most people are either stuck on 49 or move onto 56+. |
@kzc Thanks for the deep dive. |
Thanks for the cli repro, very useful indeed. There's two issues here. You're running into a bug (fixed here) in the V8's slow-path implementation of More problematic though may be that reduce_vars is moving all regexps onto the slow path by modifying And FYI: in recent V8 versions, slow path checks have become even more restrictive and now trigger whenever a property is modified or added to |
@schuay would you mind elaborating on how |
The relevant part is actually I didn't dig deeper, but |
@schuay thanks for the pointer 👍 |
@schuay Thanks for the detailed analysis and confirmation of the V8 RegExp bug in Chrome 51 and Node 6. In light of these findings, I suggest that either this RegExp literal substitution be removed altogether or to put it behind a new compress option @bdwain Thanks for the detailed bug report for this very obscure bug! I wonder why |
No problem. Thanks for jumping on the issue so quickly. I'm totally confused why this would happen also. Though from @schuay 's comments I thought the RegExp prototype was actually the one being changed. In my examples, i was able to do something like this.
and it still failed. though i guess if the prototype was changed and not replaced, that would still. make sense |
Ah okay, I missed that. Same question, different function. |
@kzc I thought @schuay meant modifying As for But I agree with improved granularity - we already have things like |
@alexlamsl so just to make sure I understand, is the reduce_vars option safe to use again? and the issue will only reappear if i also enable unsafe_regexp? |
also this fix will not be made in uglify 2 right? only v3? |
Yup, in the upcoming release.
It's a simple patch, so backporting to v2 shouldn't be too much trouble. And I doubt anybody relies on this breaking change to function before (Famous Last Words:tm:) |
cool. yea while it is a breaking change, it's breaking in that it fixes something that was broken by default, so i think it's fine to backport. thanks for fixing this issue so quickly! i really appreciate it. |
I think there's still some confusion about why regexps can take the slow path, let me try to clear that up. When running any regexp function (exec, Symbol.match, Symbol.search, test, Symbol.replace, Symbol.split), V8 checks whether the regexp instance is in a known, unmodified shape. If so, it allows us to take certain shortcuts in the implementation that are much faster than if we need to handle the generic case. The current conditions for the fast path are:
Modifying RegExp.prototype is a particularly bad case, as it will move every RegExp in the entire page onto the slow path: const re_ctor = RegExp.prototype.constructor;
const x = /./;
// All fast.
x.test("");
/./.test("");
RegExp.prototype.constructor = function(a,b) {
Reflect.construct(re_ctor, a, b);
};
// All slow.
x.test(""); // Even this one, constructed before the RE.prototype modification.
/./.test("");
RegExp(".").test("");
new RegExp(".").test(""); Regardless of how much runtime regexps take up on the web in general, it's probably a bad idea to slow them all down.
There shouldn't be a difference between the two. As far as I understand, RE.prototype modification is the only issue here :) |
@schuay thanks for the clarifications 👍 |
There's also the issue of object identity which makes replacement of RegExp literal variables inherently unsafe:
In any case, RegExp literal substitution is disabled in Uglify by default as of |
Bug report or feature request?
Bug report
ES5 or ES6+ input?
Es5
Uglify version (
uglifyjs -V
)2.8.26
JavaScript output or error produced.
The native string split method seems to no longer work correctly in the console (or my app) when i load my app. This only happens in chrome 51.
expected behavior (this is in the console after loading my app. uglify is not processing this code directly):
actual behavior:
How to reproduce
I ran create-react-app with the canary build of react-scripts (because it needs webpack 2 to reproduce the issue) and the only change i had to make was importing babel-polyfill in the entry point to break it. Removing react and all of the css did not fix it. It does not happen in development mode builds. Only production builds. The issue goes away if the build is not minified. Here is the code to reproduce if you just want to clone it. https://github.com/bdwain/uglify-bug.
the full source is just
The issue seems to have started due to the suggestion in this comment on an uglify thread
The suggestion was to replace
ast = ast.transform(compress)
withast = compress.compress(ast);
I do not believe this is due to create-react-app's usage of the webpack plugin specifically because it happens in my own app which is not based on create-react-app. I was able to reproduce it with no options passed to the plugin.
It seems to also be a bug in chrome 51 (because no matter what we do in javascript, native methods should not break), but because it only is exposed when running uglify, it seems relevant. And while I realize that the issue is only appearing when running the webpack uglify plugin, the reason I did not file the issue with them is that it seems to have started when they made a change that was suggested here (same comment as above)
UPDATE: I removed create-react-app and webpack from the example. Now, the example is just uglifying the babel-polyfill with the reduce_vars option enabled (which seems to be the source of the issue). Including that minfied result in index.html causes the issue.
The text was updated successfully, but these errors were encountered: