tailwindlabs / tailwindcss-jit

MIT License
2.19k stars 40 forks source link

Some string in codebase causing candidatePermutations to stack overflow #172

Closed calebmer closed 3 years ago

calebmer commented 3 years ago

What version of @tailwindcss/jit are you using?

v0.1.13

What version of Node.js are you using?

v14.15.0

What browser are you using?

n/a

What operating system are you using?

macOS

Reproduction repository

n/a

I have the following line of code in my codebase: [^${char}\\s][^${char}]*[^${char}\\s] (it’s used to build a regular expression). When I remove it from my codebase, @tailwindcss/jit works fine. When I add it back Tailwind CSS fails to build with:

RangeError: Maximum call stack size exceeded
    at candidatePermutations.next (<anonymous>)
    at candidatePermutations.next (<anonymous>)
    at candidatePermutations.next (<anonymous>)
    at candidatePermutations.next (<anonymous>)
    at candidatePermutations.next (<anonymous>)
    at candidatePermutations.next (<anonymous>)
    at candidatePermutations.next (<anonymous>)

I reproduced this issue by taking the implementation of candidatePermutations() in my node_modules folder and running it standalone:

function* candidatePermutations(candidate, lastIndex = Infinity) {
  if (lastIndex < 0) {
    return
  }

  let dashIdx

  if (candidate.endsWith(']', lastIndex + 1)) {
    dashIdx = candidate.lastIndexOf('[') - 1
  } else {
    dashIdx = candidate.lastIndexOf('-', lastIndex)
  }

  if (dashIdx < 0) {
    return
  }

  let prefix = candidate.slice(0, dashIdx)
  let modifier = candidate.slice(dashIdx + 1)

  yield [prefix, modifier]

  yield* candidatePermutations(candidate, dashIdx - 1)
}

for (const result of candidatePermutations('[^${char}\\\\s][^${char}]*[^${char}\\\\s]')) {
  console.log(result);
}

(Babel playground)

calebmer commented 3 years ago

Simplest string that reproduces the infinite iteration: ]-[].

adamwathan commented 3 years ago

Simplest string that reproduces the infinite iteration: ]-[].

Now this is a maintainer's dream in terms of minimal reproduction 🙏 Looking into it now, thanks.

calebmer commented 3 years ago

I think it might be a typo here:

https://github.com/tailwindlabs/tailwindcss-jit/blob/a54c6fdca05fd0568d676d5d3c39df80a114b974/src/lib/generateRules.js#L26-L30

This code works for me locally:

if (candidate.endsWith("]", lastIndex + 1)) {
  dashIdx = candidate.lastIndexOf("[", lastIndex) - 1;
} else {
  dashIdx = candidate.lastIndexOf("-", lastIndex);
}

Line 28 is missing lastIndex.

adamwathan commented 3 years ago

Should be fixed in v0.1.14! Used a slightly different solution, basically only worry about the square bracket stuff on the first pass when looking at the full string, since we don't support arbitrary values in the middle of class candidates anyways (like bg-[#126546]-foo-bar isn't valid) 👍