current commit
261025.2 · diagnostics_channel: fix dangling binding pointer
'use strict';

// A statement left unfinished at exit is finalized by the destructor chain,
// which runs after the diagnostics channel binding has been destroyed. SQLite
// invokes the profile callback for such a statement, so the channel must not
// read through the destroyed binding.

const common = require('../common');
common.skipIfSQLiteMissing();

const { spawnSyncAndExitWithoutError } = require('../common/child_process');

// Stays reachable for the rest of the process so the statement is finalized
// during teardown rather than by the garbage collector.
const keepAlive = [];

function leaveStatementUnfinished() {
  const dc = require('node:diagnostics_channel');
  const { Database } = require('node:sqlite');

  dc.subscribe('sqlite.db.query', () => {});

  const db = new Database(':memory:');
  db.exec('CREATE TABLE t (x INTEGER)');
  const insert = db.prepare('INSERT INTO t VALUES (?)');
  for (let i = 0; i < 200; i++) insert.run(i);

  const iterator = db.prepare('SELECT x FROM t').iterate();
  iterator.next();
  keepAlive.push({ db, iterator });
}

switch (process.argv[2]) {
  case 'main':
    leaveStatementUnfinished();
    break;

  case 'worker': {
    const { Worker, isMainThread } = require('node:worker_threads');
    if (isMainThread) {
      new Worker(__filename, { argv: ['worker'] });
    } else {
      leaveStatementUnfinished();
    }
    break;
  }

  default:
    spawnSyncAndExitWithoutError(process.execPath, [__filename, 'main']);
    spawnSyncAndExitWithoutError(process.execPath, [__filename, 'worker']);
}