bytenode/bytenode

Electron crashes on any undefined value

orcunxrd opened this issue · 15 comments

Hello. When there is any undefined value in my Electron project, which I compiled with Bytenode, the program closes directly. Is there a solution to this?

Please provide a reproducible example for your issue.

@OsamaAbbas It is difficult for me to explain my situation, but I will share a similar situation as an example.
The following code causes the Electron project compiled with Bytenode to close:

const test = null;
const example = test.obj.example;

I cannot reproduce the issue.

How do you use bytenode with electron? Can you make a very simple setup using the getting-started electron example?

Sure. I use it like this:

"use strict";

const bytenode = require("bytenode");
const fs = require("fs");
const v8 = require("v8");
const path = require("path");

v8.setFlagsFromString("--no-lazy");

if (!fs.existsSync(path.join(__dirname, "./background.jsc"))) {
  bytenode.compileFile(
    path.join(__dirname, "./background.src.js"),
    path.join(__dirname, "./background.jsc")
  );
}

require(path.join(__dirname, "./background.jsc"));

Here is my complete setup that can be run by: npm install, npm run compile, and npm run start.

// package.json
{
  "name": "electron-issue-212",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "compile": "bytenode -c -e protected.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bytenode": "^1.4.0",
    "electron": "^24.0.0"
  }
}
// main.js
'use strict';

const path = require('path');
const { app, BrowserWindow } = require('electron');

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: true,
      contextIsolation: false,
      sandbox: false
    }
  });

  win.loadFile('index.html');
};

app.whenReady().then(() => {
  createWindow();
});
<!-- index.html -->
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
  <title>Hello World!</title>
</head>

<body>
  <h1>Hello World!</h1>
  We are using Node.js <span id="node-version"></span>,
  Chromium <span id="chrome-version"></span>,
  and Electron <span id="electron-version"></span>.
</body>

</html>
// preload.js
'use strict';

require('bytenode');
// run the source file
// require('./protected.js')(window);
// run the compiled file
require('./protected.jsc')(window);
// protected.js
'use strict';

module.exports = function (window) {

  window.addEventListener('DOMContentLoaded', () => {
    const replaceText = (selector, text) => {
      const element = document.getElementById(selector);
      if (element) element.innerText = text;
    };

    for (const dependency of ['chrome', 'node', 'electron']) {
      replaceText(`${dependency}-version`, process.versions[dependency]);
    }

    // here is your code
    const test = null;
    const example = test.obj.example;
  });
};

Now, if you run the app and open the Developer Tools, you will find the uncaught TypeError in the console. The program does not crash. It's the same outcome with protected.jsc or protected.js files.

Thanks for your example, I'll try it right away. I don't just use electron, I use this:
https://blog.logrocket.com/building-app-electron-vue/

Is there any difference between them?

Well, there is no "standard" way to integrate bytenode with electron. Let me check and see how to protect your background.js file.

In that setup, are there any other files or components you want to protect?

I did not fully explain the problem. I will explain all of them now. When I get the build, the whole electron project is in the background.js file.

image

Before I get the build in the Vue.config.js file, I run the build function I wrote for the bytenode.
image

The working logic compiles the background.js file with Bytenode. In order to run the resulting background.jsc file, it creates a background.js file again and writes the following code in it:

"use strict";

const bytenode = require("bytenode");
const fs = require("fs");
const v8 = require("v8");
const path = require("path");

v8.setFlagsFromString("--no-lazy");

if (!fs.existsSync(path.join(__dirname, "./background.jsc"))) {
  bytenode.compileFile(
    path.join(__dirname, "./background.src.js"),
    path.join(__dirname, "./background.jsc")
  );
}

require(path.join(__dirname, "./background.jsc"));

The problem I'm having is only valid for build. And I don't have this problem in renderer. I just live in the backend. You should try the example I gave for you in the backend, not the renderer. And you should try it after you get the build. In dev mode it prints the error to the screen and there is no problem. But when I build, the program crashes directly.

Change the example you gave me above to:

// main.js
'use strict';

const path = require('path');
const { app, BrowserWindow } = require('electron');

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: true,
      contextIsolation: false,
      sandbox: false
    }
  });

  win.loadFile('index.html');
  
  const test = null; // Exception
  const example = test.obj.example; // Exception
};

app.whenReady().then(() => {
  createWindow();
});

Get Build and run the program. Your program will most likely crash.

Again, I cannot reproduce the issue.

Here are the changes on my example:

// package.json
...
  "main": "main.jsc",
  "scripts": {
    "start": "electron -r bytenode .",
    "compile": "bytenode -c -e main.js protected.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
...
'use strict';

const path = require('path');
const { app, BrowserWindow } = require('electron');

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: true,
      contextIsolation: false,
      sandbox: false
    }
  });

  win.loadFile('index.html');

  const test = null;
  const example = test.obj.example;
};

app.whenReady().then(() => {
  createWindow();
});

Run npm run compile and npm run start. Both main.js and main.jsc have the same outcome. The error is logged on my terminal, no crashes.

So it's clearly specific to the vue+electron setup. I will have a look and get back to you. Probably tomorrow though.

I have identified the problem. When it is inside an async function, the program is closing. @OsamaAbbas

There is no problem when ipcMain is not asynchronous:

ipcMain.on('testEvent', (_event) => {
  const test = null;
  const example = test.obj.example;
});

But if ipcMain is run asynchronously, the program will close:

ipcMain.on('testEvent', async (_event) => {
  const test = null;
  const example = test.obj.example;
})

As I mentioned, this problem only occurs in the compiled version.

Arrow functions are not compatible with bytenode, especially async arrow functions. This is mentioned in the README.md file.

I'm closing this issue as I believe changing your arrow functions into regular functions will solve it.

I believe the problem is not limited to ipcMain, but affects all asynchronous functions. Please paste the following code into your main.js file, compile it with Bytenode, build it, and try to run it:

// main.js
'use strict';

const path = require('path');
const { app, BrowserWindow } = require('electron');

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: true,
      contextIsolation: false,
      sandbox: false
    }
  });

  win.loadFile('index.html');

  const test = null;
  const example = test.obj.example;
};

setTimeout(async () => {
  const test = null;
  const example = test.obj.example;
}, 1);

app.whenReady().then(() => {
  createWindow();
});

I believe the problem is not limited to ipcMain, but affects all asynchronous functions.

It affects all arrow functions. Sometimes they work, but your program will crash eventually. V8 engine inspects arrow functions' source code under certain conditions, and when the source code isn't there (because bytenode removes it), it crashes.