Repository: sveltejs/svelte (JavaScript)
Date: 2019-12-10 15:50:51+00:00
Task type: Bug Fix

Issue:
Application won't compile - "cannot read property 'n' of undefined" since 3.16.1
**Describe the bug**

Upgrading an existing app to Svelte 3.16.1 causes client compilation to fail with the error `Cannot read property 'n' of undefined`.

I'm the second person to see this in the discord, it seems. I don't know where the error comes from or what the causes is (but I do see that the variable `n` is used a lot in recent Svelte commits - https://github.com/sveltejs/svelte/commit/bb5cf9ada7706fed9bb86467d2ae78c76f88b9d0).

**Logs**

ant@xeno  ~/Projects/beyonk-dashboard   master ●  npm run dev                         

> beyonk-dashboard@0.3.0 dev /home/ant/Projects/beyonk-dashboard
> PORT=1233 NODE_CONFIG_ENV=${NODE_ENV} sapper dev

✗ client
Cannot read property 'n' of undefined

**To Reproduce**
I'm afraid I simply don't know, at present.

**Expected behavior**
The client should compile as normal.

**Information about your Svelte project:**
- Your operating system: Ubuntu 19.04

- Svelte version 3.16.1

- Rollup

**Severity**
Blocker. It's broken, and my app won't start.

**Additional context**
The app was previously using 3.15, and trying 3.16.0 wouldn't start either, due to an issue with `reduce with no initial value`.



Issue discussion:
Presumably #4063 was an incomplete fix. Please do update with a repro if you find one.
This script should try to compile all components in the project and allow the full stack trace and breakpoints using the built-in Node debugger.

Not sure if it needs tweaking for sapper though.

```
const svelte = require('svelte/compiler');
const fs     = require('fs');
const glob   = require('glob');

function do_compile(err,data) {
    if (err) {
        console.log("Couldn't read file:", err);
        process.exit(-1);
    }

    let result = svelte.compile(data);
}

function call_svelte(err, files) {
    if (err) {
        console.log("Glob error:", err);
        process.exit(-1);
    }

    files.forEach(f => {
        fs.readFile(f, {encoding:"utf8"}, do_compile);
    });
}

glob('src/*.svelte', call_svelte);
glob('src/**/*.svelte', call_svelte);
```
I believe this line is the issue:

https://github.com/sveltejs/svelte/blob/8a6abc9215b10e93db90c0d33c2b2f99d098641e/src/compiler/compile/render_dom/Renderer.ts#L256

We could temporarily fix it by checking for `bitmask[0]`, but I don't know what causes this to happen in the first place... ~~I _believe_ the issue is that `context_overflow` is set _after_ the `Block` and `FragmentWrapper` are created and they use `dirty`, but don't they also append to the context? So idk the right approach here.~~
@mrkishi,

This line should probably just initialize bitmask to contain an object since we'll always need at least one of them.

https://github.com/sveltejs/svelte/blob/8a6abc9215b10e93db90c0d33c2b2f99d098641e/src/compiler/compile/render_dom/Renderer.ts#L209

Should be:
```
const bitmask: BitMasks = [{ n: 0, names: [] }]; 
```

Burning question is still how did the bitmask object get initialized not containing an index  0 in the first place!
Contrary to what I assumed earlier, I see that `renderer.add_to_context()` is called in a few places after initialization, _during_ render. That'd explain why `context_overflow` is `false` even though there are more than 31 variables in the context.
Here's a reproduction: https://svelte.dev/repl/5357a38fcfc6441ebcf162fa5d307a12?version=3.16.1 

This just imports the `svelte-select` library. I haven't reduced this to a minimal test case, but hopefully it helps you figure this out. It appears to be failing on the file **List.svelte** inside `svelte-select`.

```
<script>
	import Select from 'svelte-select';
</script>
```
I took the code from @dimfeld 's repro (thanks!) and took the `List.svelte` component out and started to trim it back.

This is as minimal as I can get it without making the error disappear:

https://svelte.dev/repl/909f45b7a2234ad2804d3dcecd59a54b?version=3.16.1

It seems to occur when you have exactly 31 variables declared and a component declaration.

Note:

* The component declaration is essential
* The number of variables is essential (31)
* The component must be declared with opening and closing tags on different lines
+ In continuation to what @antony  said above, the issue is also caused by a carriage return of an empty component, like so:
```html
<A>

</A>
```

The error goes away if you write `<A></A>`
> * In continuation to what @antony  said above, the issue is also caused by a carriage return of an empty component, like so:
> 
> ```
> <A>
> 
> </A>
> ```
> 
> The error goes away if you write `<A></A>`

This is what I meant by point #3 above. It's not the sole cause though. You also need exactly 31 variable declarations.
Great, thanks for the minimal repro, folks!

edit: It looks like the necessary condition for this to manifest is actually that there be _some_ content passed to the component. The carriage return isn't important, but it does count as content to be passed to the child component.
One way to address this looks like it would be to replace

```js
if (!bitmask[i]) bitmask[i] = { n: 0, names: [] };
```

with

```js
if (bitmask.length <= i) {
	for (let j = bitmask.length; j <= i; j++) {
		bitmask[j] = { n: 0, names: [] };
	}
}
```

so that we fill in the missing elements in the `bitmask` array, but I'm not sure whether this is just covering up a bug elsewhere.

Solution patch:
diff --git a/src/compiler/compile/render_dom/Renderer.ts b/src/compiler/compile/render_dom/Renderer.ts
--- a/src/compiler/compile/render_dom/Renderer.ts
+++ b/src/compiler/compile/render_dom/Renderer.ts
@@ -86,8 +86,6 @@ export default class Renderer {
 			null
 		);
 
-		this.context_overflow = this.context.length > 31;
-
 		// TODO messy
 		this.blocks.forEach(block => {
 			if (block instanceof Block) {
@@ -99,6 +97,8 @@ export default class Renderer {
 
 		this.fragment.render(this.block, null, x`#nodes` as Identifier);
 
+		this.context_overflow = this.context.length > 31;
+
 		this.context.forEach(member => {
 			const { variable } = member;
 			if (variable) {


Test patch:
diff --git a/test/runtime/samples/bitmask-overflow-3/_config.js b/test/runtime/samples/bitmask-overflow-3/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/bitmask-overflow-3/_config.js
@@ -0,0 +1,3 @@
+export default {
+	error: `A is not defined`,
+};
\ No newline at end of file
diff --git a/test/runtime/samples/bitmask-overflow-3/main.svelte b/test/runtime/samples/bitmask-overflow-3/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/bitmask-overflow-3/main.svelte
@@ -0,0 +1,4 @@
+<script>
+	let x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, x30, x31;
+</script>
+<A>foo</A>