From 419cfd493b3007245d2ed0cce2906acf853b57c7 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Sat, 3 Jan 2026 23:19:30 +0100 Subject: [PATCH 1/3] sqlite: change approach to fix segfault SQLTagStore --- src/node_sqlite.cc | 15 ++++++----- src/node_sqlite.h | 10 ++++++- test/parallel/test-sqlite-template-tag.js | 33 +++++++++++++++++++++++ 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 6d35236dce0f82..9bd59319c2b4f9 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -831,8 +831,8 @@ void DatabaseSync::CreateTagStore(const FunctionCallbackInfo& args) { capacity = args[0].As()->Value(); } - BaseObjectPtr session = - SQLTagStore::Create(env, BaseObjectWeakPtr(db), capacity); + BaseObjectPtr session = SQLTagStore::Create( + env, BaseObjectWeakPtr(db), args.This(), capacity); if (!session) { // Handle error if creation failed THROW_ERR_SQLITE_ERROR(env->isolate(), "Failed to create SQLTagStore"); @@ -2710,7 +2710,10 @@ Local SQLTagStore::GetConstructorTemplate(Environment* env) { } BaseObjectPtr SQLTagStore::Create( - Environment* env, BaseObjectWeakPtr database, int capacity) { + Environment* env, + BaseObjectWeakPtr database, + Local database_object, + int capacity) { Local obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() @@ -2718,6 +2721,7 @@ BaseObjectPtr SQLTagStore::Create( .ToLocal(&obj)) { return nullptr; } + obj->SetInternalField(kDatabaseObject, database_object); return MakeBaseObject(env, obj, std::move(database), capacity); } @@ -2728,9 +2732,8 @@ void SQLTagStore::CapacityGetter(const FunctionCallbackInfo& args) { } void SQLTagStore::DatabaseGetter(const FunctionCallbackInfo& args) { - SQLTagStore* store; - ASSIGN_OR_RETURN_UNWRAP(&store, args.This()); - args.GetReturnValue().Set(store->database_->object()); + args.GetReturnValue().Set( + args.This()->GetInternalField(kDatabaseObject).As()); } void SQLTagStore::SizeGetter(const FunctionCallbackInfo& args) { diff --git a/src/node_sqlite.h b/src/node_sqlite.h index 2641c9d4f1e8c5..287924841ff4f7 100644 --- a/src/node_sqlite.h +++ b/src/node_sqlite.h @@ -304,13 +304,21 @@ class Session : public BaseObject { class SQLTagStore : public BaseObject { public: + enum InternalFields { + kDatabaseObject = BaseObject::kInternalFieldCount, + kInternalFieldCount + }; + SQLTagStore(Environment* env, v8::Local object, BaseObjectWeakPtr database, int capacity); ~SQLTagStore() override; static BaseObjectPtr Create( - Environment* env, BaseObjectWeakPtr database, int capacity); + Environment* env, + BaseObjectWeakPtr database, + v8::Local database_object, + int capacity); static v8::Local GetConstructorTemplate( Environment* env); static void All(const v8::FunctionCallbackInfo& args); diff --git a/test/parallel/test-sqlite-template-tag.js b/test/parallel/test-sqlite-template-tag.js index f640e70f8c399a..f34d1b059a908e 100644 --- a/test/parallel/test-sqlite-template-tag.js +++ b/test/parallel/test-sqlite-template-tag.js @@ -1,4 +1,6 @@ 'use strict'; +// Flags: --expose-gc + const { skipIfSQLiteMissing } = require('../common'); skipIfSQLiteMissing(); @@ -124,3 +126,34 @@ test('sql error messages are descriptive', () => { message: /no such table/i, }); }); + +test('a tag store keeps the database alive by itself', () => { + const sql = new DatabaseSync(':memory:').createTagStore(); + + sql.db.exec('CREATE TABLE test (data INTEGER)'); + + global.gc(); + + // eslint-disable-next-line no-unused-expressions + sql.run`INSERT INTO test (data) VALUES (1)`; +}); + +test('tag store prevents circular reference leaks', async () => { + const { gcUntil } = require('../common/gc'); + + const before = process.memoryUsage().heapUsed; + + // Create many SQLTagStore + DatabaseSync pairs with circular references + for (let i = 0; i < 1000; i++) { + const sql = new DatabaseSync(':memory:').createTagStore(); + sql.db.exec('CREATE TABLE test (data INTEGER)'); + sql.db.setAuthorizer(() => void sql.db); + } + + // GC until memory stabilizes or give up after 20 attempts + await gcUntil('tag store leak check', () => { + const after = process.memoryUsage().heapUsed; + // Memory should not grow significantly (allow 50% margin for noise) + return after < before * 1.5; + }, 20); +}); \ No newline at end of file From 7c71345e0c425698b0de236d1b03923ecd256246 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Sat, 3 Jan 2026 23:31:19 +0100 Subject: [PATCH 2/3] sqlite: add eslint ignore for no-void --- test/parallel/test-sqlite-template-tag.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-sqlite-template-tag.js b/test/parallel/test-sqlite-template-tag.js index f34d1b059a908e..f9f1d9936c38b1 100644 --- a/test/parallel/test-sqlite-template-tag.js +++ b/test/parallel/test-sqlite-template-tag.js @@ -147,6 +147,7 @@ test('tag store prevents circular reference leaks', async () => { for (let i = 0; i < 1000; i++) { const sql = new DatabaseSync(':memory:').createTagStore(); sql.db.exec('CREATE TABLE test (data INTEGER)'); + // eslint-disable-next-line no-void sql.db.setAuthorizer(() => void sql.db); } @@ -156,4 +157,4 @@ test('tag store prevents circular reference leaks', async () => { // Memory should not grow significantly (allow 50% margin for noise) return after < before * 1.5; }, 20); -}); \ No newline at end of file +}); From 20a0eed2c829bd488c45281f7f8933078be66edd Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Sun, 4 Jan 2026 00:25:53 +0100 Subject: [PATCH 3/3] sqlite: remove redundant parameter --- src/node_sqlite.cc | 11 ++++------- src/node_sqlite.h | 5 +---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 9bd59319c2b4f9..c46ddaeb8f418b 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -831,8 +831,8 @@ void DatabaseSync::CreateTagStore(const FunctionCallbackInfo& args) { capacity = args[0].As()->Value(); } - BaseObjectPtr session = SQLTagStore::Create( - env, BaseObjectWeakPtr(db), args.This(), capacity); + BaseObjectPtr session = + SQLTagStore::Create(env, BaseObjectWeakPtr(db), capacity); if (!session) { // Handle error if creation failed THROW_ERR_SQLITE_ERROR(env->isolate(), "Failed to create SQLTagStore"); @@ -2710,10 +2710,7 @@ Local SQLTagStore::GetConstructorTemplate(Environment* env) { } BaseObjectPtr SQLTagStore::Create( - Environment* env, - BaseObjectWeakPtr database, - Local database_object, - int capacity) { + Environment* env, BaseObjectWeakPtr database, int capacity) { Local obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() @@ -2721,7 +2718,7 @@ BaseObjectPtr SQLTagStore::Create( .ToLocal(&obj)) { return nullptr; } - obj->SetInternalField(kDatabaseObject, database_object); + obj->SetInternalField(kDatabaseObject, database->object()); return MakeBaseObject(env, obj, std::move(database), capacity); } diff --git a/src/node_sqlite.h b/src/node_sqlite.h index 287924841ff4f7..f31af86aebc871 100644 --- a/src/node_sqlite.h +++ b/src/node_sqlite.h @@ -315,10 +315,7 @@ class SQLTagStore : public BaseObject { int capacity); ~SQLTagStore() override; static BaseObjectPtr Create( - Environment* env, - BaseObjectWeakPtr database, - v8::Local database_object, - int capacity); + Environment* env, BaseObjectWeakPtr database, int capacity); static v8::Local GetConstructorTemplate( Environment* env); static void All(const v8::FunctionCallbackInfo& args);