babel / minify

:scissors: An ES6+ aware minifier based on the Babel toolchain (beta)
https://babeljs.io/repl
MIT License
4.39k stars 225 forks source link

Constant with regular expression literal with global modifier incorrectly replace by literal #1026

Open bryan-kramer opened 2 years ago

bryan-kramer commented 2 years ago

Describe the bug

The code contains a constant whose value is a regular expression with the g (i.e. global) modifier. Minify replaces the use of this constant with the literal regular expression resulting in an infinite loop.

To Reproduce

Minimal code to reproduce the bug

/**
 * Incorrect minimization of regular expression
 *
 */

function test(data) {
  var re = /a/g;

  let match;
  let result = [];

  while ((match = re.exec(data))) {
    result.push(match);
  }

  return result;
}

console.log(test('abababab'));

Actual Output

function test(a){let b,c=[];for(;b=/a/g.exec(a);)c.push(b);return c}console.log(test("abababab"));

The minifier generates exactly the same code if the regular expression literal is replaced by new Regexp('a', 'g').

Expected Output

function test(a){let b=/a/g,c,d=[];for(;c=b.exec(a);)c.push(c);return d}console.log(test("abababab"));

Configuration

How are you using babel-minify?

npx babel --no-comments js/script.js

babel-minify version: 0.5.0

babel version : 7.17.6 (@babel/core 7.17.9)

babel-minify-config: none

babelrc:

{
  "presets": [
    [
      "minify",
      {
        "evaluate": false 
      }
    ]
  ]
}

Possible solution

The while loop must use the same RegExp object through each iteration. At a minimum, the babel minimizer should recognize that the global modifier is present and make sure that the same regular expression object is used.

It turns out that the minifier does the right thing when it cannot determine the modifiers in the case where modifiers is a function parameter, e.g.

function f(modifiers) {
   const re = new RegExp('a', modifiers)

Additional context

In this case the minified code results in a infinite loop at least when run in latest versions of Chrome (Version 102.0.5001.0 (Official Build) canary (64-bit)) and with nodejs (v16.14.0).

It seems that the JavaScript engines must be recreating the regular expression object through each loop iteration or at least resetting the index.

I tried each of the minimizer options that looked relevant one at a time to see if any fixed this problem.

This fix needs to happen in any situation that uses the regular expression

const a = /a/g;
let b = 'ababa';

if (a.exec(b)) { ....}
if(a.exec(b)){ ... }