mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
c4b755ee00 tracks the statements currently being stepped and rejects
reentry into them, but the guard is established inside the execution
helpers, after the entry point has already reset the statement and
bound its parameters. Binding reads properties off the supplied
object, so a named-parameter getter runs JavaScript in that window.
Reentering the same statement there resets it a second time and, for
iterate(), hands out a second iterator; both iterators record the
same reset generation, so neither is invalidated and they interleave
rows from one virtual machine.
Establish the guard at the four StatementSync entry points instead,
before the reset, so it spans binding as well as stepping. The
existing guards inside the helpers are left in place; the stepping
set is a stack, so the nested acquisition is balanced.
Signed-off-by: Trevor Burnham <trevorburnham@gmail.com>
Assisted-by: claude:opus-5
PR-URL: https://github.com/nodejs/node/pull/65294
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
64 lines
1.9 KiB
JavaScript
64 lines
1.9 KiB
JavaScript
'use strict';
|
|
|
|
const { skipIfSQLiteMissing, mustCall } = require('../common');
|
|
skipIfSQLiteMissing();
|
|
const assert = require('node:assert');
|
|
const { test } = require('node:test');
|
|
const { DatabaseSync } = require('node:sqlite');
|
|
|
|
const reentryError = {
|
|
code: 'ERR_INVALID_STATE',
|
|
message: 'statement is already being executed',
|
|
};
|
|
|
|
// Binding a named parameter reads properties off the supplied object, so a
|
|
// getter runs JavaScript after the statement has been reset but before it is
|
|
// stepped. Reentering the same statement there resets it a second time and
|
|
// hands out a second iterator over one virtual machine.
|
|
for (const method of ['all', 'get', 'run', 'iterate']) {
|
|
test(`${method}() reentry during parameter binding is rejected`, () => {
|
|
const db = new DatabaseSync(':memory:');
|
|
db.exec(`
|
|
CREATE TABLE data (value INTEGER);
|
|
INSERT INTO data VALUES (1), (2), (3);
|
|
`);
|
|
|
|
let statement;
|
|
const invoke = (params) => (method === 'iterate' ?
|
|
[...statement.iterate(params)] :
|
|
statement[method](params));
|
|
const reenter = mustCall(() => {
|
|
assert.throws(() => invoke({ $min: 2 }), reentryError);
|
|
return 1;
|
|
});
|
|
const params = { get $min() { return reenter(); } };
|
|
|
|
statement = db.prepare('SELECT value FROM data WHERE value >= $min');
|
|
invoke(params);
|
|
});
|
|
}
|
|
|
|
test('two iterators cannot share one virtual machine', () => {
|
|
const db = new DatabaseSync(':memory:');
|
|
db.exec(`
|
|
CREATE TABLE data (value INTEGER);
|
|
INSERT INTO data VALUES (1), (2), (3);
|
|
`);
|
|
|
|
const statement = db.prepare('SELECT value FROM data WHERE value >= $min');
|
|
let inner;
|
|
const reenter = mustCall(() => {
|
|
assert.throws(() => {
|
|
inner = statement.iterate({ $min: 1 });
|
|
}, reentryError);
|
|
return 1;
|
|
});
|
|
const params = { get $min() { return reenter(); } };
|
|
|
|
assert.deepStrictEqual(
|
|
[...statement.iterate(params)].map((row) => row.value),
|
|
[1, 2, 3],
|
|
);
|
|
assert.strictEqual(inner, undefined);
|
|
});
|