Subscribe on changes!

@vue/shared generateCodeFrame can infinite loop, breaking a build process.

avatar
Jun 21st 2021

Version

3.1.1

Reproduction link

https://github.com/DV8FromTheWorld/vue3-sfc-compiler-bug-repro-template

Steps to reproduce

  1. Pull the repo and npm install it.
  2. Run npm run serve
  3. Observe how the build process gets stuck and never completes when it is handing the BrokenComponent.vue file.

What is expected?

The build should complete and generate warnings about using undecorated <template> elements in Vue3.

What is actually happening?

The build hangs forever due to an infinite loop when generating the code frame to display with the vue-migration compatibility warning.


I originally encountered this problem when transitioning my application from Vue 2.6 to @vue/compat: 3.1.1. The problem surfaced as a Webpack build that would never complete the compilation step. The Vue-loader would successfully separate the SFC into the specific template/script/style portions and pitch them, but when compiling the template (thus generating migration warnings) the system will get stuck.

In the screenshot below observe that the generateCodeFrame will infinite loop. image

This will infinite loop because j >= lines.length will resolve to true and hit the continue statement without incrementing count any further.

It is possible that the culprit causing this problem is not so much the generateCodeFrame function itself but instead the function calculating the loc for the start and end of the <template> ... </template>. From my testing, the loc.*.column and loc.*.line for both loc.start and loc.end were accurate, however the loc.*.offset for both seemed to be off by 75 - 110 characters. The generateCodeFrame code uses offset as it's source of truth here and thus could be the problem here.

Additionally, another issue caused by this oddity with the offset is that the code frame actually generated has the ^^^^^^^ pointers multiple times in a very confusing fashion. This is caused by the count not exceeding end fast enough. image

avatar
Jun 22nd 2021

The provided reproduction gives this:Screenshot 2021-06-22 at 11 08 52 and it doesn't hang in an infinite loop.

The loop you showed still increments j when it continues but maybe end > count never changes, probably creating the infinite loop you saw

avatar
Jun 22nd 2021

The loop indeed does increment j but yes, as mentioned before, the problem is that end > count never changes. You can see that the debugger is currently stopped in a state of infinite loop by comparing the values shown in the Scope tab on the right.

I just freshly pulled the repository, ran npm i, and npm run serve. I was able to see the error still:

image

Gif showing it happen vue-infinite-loop-example

avatar
Jun 22nd 2021

I'm not sure what more information I can provide given that the repo does break for me. What direction would you like to pursue for this?

avatar
Jun 22nd 2021

make sure you update the dependencies then. I used yarn to install them

avatar
Jun 22nd 2021

Cleared my node_modules, installed yarn, ran yarn install and yarn run serve. Resulted in the same behavior.

Here is the generated yarn.lock file if you'd like to do a diff between it and your own. https://gist.github.com/DV8FromTheWorld/8bd1dc6dc8288b86663630b44f466324

avatar
Jun 22nd 2021

...I bet this has to do with the way the parser is consuming characters and adjusting offset. This probably is a difference between \n and \r\n given you are on unix and I am on windows.

avatar
Jun 22nd 2021

Yeah, so the problem here is: https://github.com/vuejs/vue-next/blob/master/packages/shared/src/codeframe.ts#L8

Basically, everything else in the parser deals with \n vs \r\n by using the .length property, thus getting 1 or 2 respectively. This split treats \n and \r\n identically, so every time another line is processed, the count will deviate from offset by 1 additional character.

If there are enough lines in the file proceeding the piece of code that the generateCodeFrame is suppose to be generating a codeframe for and not enough content after the fragment to make up for the disparity then this will loop forever.