Skip to content

Commit b2b7405

Browse files
committed
sqlite: reject deserialize() while in a callback
deserialize() could be called from a user-defined function invoked during statement execution, tearing down the database connection while sqlite3_step() was still using it. Reuse the existing callback depth check to throw ERR_INVALID_STATE instead, matching the guard already in place for close(). Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: codex:gpt-5.6-sol PR-URL: #64796 Refs: #64795 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
1 parent bf2f995 commit b2b7405

3 files changed

Lines changed: 26 additions & 2 deletions

File tree

doc/api/sqlite.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -629,8 +629,10 @@ added:
629629
Loads a serialized database into this connection, replacing the current
630630
database. The deserialized database is writable. Existing prepared statements
631631
are finalized before deserialization is attempted, even if the operation
632-
subsequently fails. This method is a wrapper around
633-
[`sqlite3_deserialize()`][].
632+
subsequently fails. An [`ERR_INVALID_STATE`][] error is thrown if the method is
633+
called while a database callback is on the stack, for example a user-defined
634+
function, an aggregate function, an authorizer, or a changeset filter or conflict
635+
handler. This method is a wrapper around [`sqlite3_deserialize()`][].
634636

635637
```mjs
636638
import { DatabaseSync } from 'node:sqlite';
@@ -1796,6 +1798,7 @@ callback function to indicate what type of operation is being authorized.
17961798
[SQL injection]: https://en.wikipedia.org/wiki/SQL_injection
17971799
[Type conversion between JavaScript and SQLite]: #type-conversion-between-javascript-and-sqlite
17981800
[`ATTACH DATABASE`]: https://www.sqlite.org/lang_attach.html
1801+
[`ERR_INVALID_STATE`]: errors.md#err_invalid_state
17991802
[`PRAGMA foreign_keys`]: https://www.sqlite.org/pragma.html#pragma_foreign_keys
18001803
[`SQLITE_DBCONFIG_DEFENSIVE`]: https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigdefensive
18011804
[`SQLITE_DETERMINISTIC`]: https://www.sqlite.org/c3ref/c_deterministic.html

src/node_sqlite.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1858,6 +1858,10 @@ void DatabaseSync::Deserialize(const FunctionCallbackInfo<Value>& args) {
18581858
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
18591859
Environment* env = Environment::GetCurrent(args);
18601860
THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open");
1861+
THROW_AND_RETURN_ON_BAD_STATE(
1862+
env,
1863+
db->IsInCallback(),
1864+
"database cannot be deserialized while in a callback");
18611865

18621866
if (!args[0]->IsUint8Array()) {
18631867
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),

test/parallel/test-sqlite-serialize.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,23 @@ suite('DatabaseSync.prototype.deserialize()', () => {
190190
});
191191
});
192192

193+
test('throws if called while in a callback', (t) => {
194+
const source = new DatabaseSync(':memory:');
195+
const serialized = source.serialize();
196+
source.close();
197+
198+
const db = new DatabaseSync(':memory:');
199+
t.after(() => db.close());
200+
db.function('deserialize_database', () => db.deserialize(serialized));
201+
const stmt = db.prepare('SELECT deserialize_database()');
202+
203+
t.assert.throws(() => stmt.get(), {
204+
code: 'ERR_INVALID_STATE',
205+
message: 'database cannot be deserialized while in a callback',
206+
});
207+
t.assert.strictEqual(db.isOpen, true);
208+
});
209+
193210
test('throws if buffer argument is not a Uint8Array', (t) => {
194211
const db = new DatabaseSync(':memory:');
195212
t.assert.throws(() => {

0 commit comments

Comments
 (0)