From 01f2928fbf94517634e41fa151b27ff696ed8f39 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Sun, 9 Aug 2026 20:44:43 -0700 Subject: [PATCH 1/2] sqlite: validate StatementSync.run() integers Use the standard SQLite integer conversion for changes and lastInsertRowid. Throw ERR_OUT_OF_RANGE when a value cannot be represented safely as a Number, or return it as a BigInt when BigInt reads are enabled. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: codex:gpt-5.6-sol --- src/node_sqlite.cc | 39 ++++++++++++--------- test/parallel/test-sqlite-statement-sync.js | 19 ++++++++++ 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 80da5bc0d9bf..a54d0d359090 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -79,6 +79,24 @@ inline MaybeLocal Utf8StringMaybeOneByte(Isolate* isolate, isolate, input.data(), NewStringType::kNormal, len); } +inline MaybeLocal IntegerToValue(Isolate* isolate, + sqlite3_int64 value, + bool use_big_ints) { + if (use_big_ints) { + return BigInt::New(isolate, value); + } + + if (value >= -kMaxSafeJsInteger && value <= kMaxSafeJsInteger) { + return Number::New(isolate, value); + } + + THROW_ERR_OUT_OF_RANGE( + isolate, + "Value is too large to be represented as a JavaScript number: %" PRId64, + value); + return MaybeLocal(); +} + #define CHECK_ERROR_OR_THROW(isolate, db, expr, expected, ret) \ do { \ int r_ = (expr); \ @@ -101,16 +119,7 @@ inline MaybeLocal Utf8StringMaybeOneByte(Isolate* isolate, switch (sqlite3_##from##_type(__VA_ARGS__)) { \ case SQLITE_INTEGER: { \ sqlite3_int64 val = sqlite3_##from##_int64(__VA_ARGS__); \ - if ((use_big_int_args)) { \ - (result) = BigInt::New((isolate), val); \ - } else if (std::abs(val) <= kMaxSafeJsInteger) { \ - (result) = Number::New((isolate), val); \ - } else { \ - THROW_ERR_OUT_OF_RANGE((isolate), \ - "Value is too large to be represented as a " \ - "JavaScript number: %" PRId64, \ - val); \ - } \ + (result) = IntegerToValue((isolate), val, (use_big_int_args)); \ break; \ } \ case SQLITE_FLOAT: { \ @@ -3018,12 +3027,10 @@ MaybeLocal StatementExecutionHelper::Run(Environment* env, Local last_insert_rowid_val; Local changes_val; - if (use_big_ints) { - last_insert_rowid_val = BigInt::New(isolate, last_insert_rowid); - changes_val = BigInt::New(isolate, changes); - } else { - last_insert_rowid_val = Number::New(isolate, last_insert_rowid); - changes_val = Number::New(isolate, changes); + if (!IntegerToValue(isolate, last_insert_rowid, use_big_ints) + .ToLocal(&last_insert_rowid_val) || + !IntegerToValue(isolate, changes, use_big_ints).ToLocal(&changes_val)) { + return MaybeLocal(); } auto run_result_template = env->sqlite_run_result_template(); diff --git a/test/parallel/test-sqlite-statement-sync.js b/test/parallel/test-sqlite-statement-sync.js index cf0e4daa45ca..07f8c1ab4f7a 100644 --- a/test/parallel/test-sqlite-statement-sync.js +++ b/test/parallel/test-sqlite-statement-sync.js @@ -556,6 +556,25 @@ suite('StatementSync.prototype.setReadBigInts()', () => { }); }); + test('BigInt is required for reading large last insert row IDs', (t) => { + using db = new DatabaseSync(':memory:'); + db.exec('CREATE TABLE data(key INTEGER PRIMARY KEY) STRICT'); + const insert = db.prepare('INSERT INTO data VALUES (?)'); + + t.assert.throws(() => { + insert.run(9007199254740993n); + }, { + code: 'ERR_OUT_OF_RANGE', + message: /^Value is too large to be represented as a JavaScript number: 9007199254740993$/, + }); + + insert.setReadBigInts(true); + t.assert.deepStrictEqual(insert.run(9007199254740995n), { + changes: 1n, + lastInsertRowid: 9007199254740995n, + }); + }); + test('throws if the statement is already finalized', (t) => { using db = new DatabaseSync(':memory:'); const stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)'); From e07659a9d1d008a78f56c526a7077f239170091f Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 10 Aug 2026 21:01:22 +0000 Subject: [PATCH 2/2] fixup! sqlite: validate StatementSync.run() integers --- src/node_sqlite.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index a54d0d359090..ef51121d387c 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -86,15 +86,15 @@ inline MaybeLocal IntegerToValue(Isolate* isolate, return BigInt::New(isolate, value); } - if (value >= -kMaxSafeJsInteger && value <= kMaxSafeJsInteger) { - return Number::New(isolate, value); + if (value < -kMaxSafeJsInteger || value > kMaxSafeJsInteger) { + THROW_ERR_OUT_OF_RANGE( + isolate, + "Value is too large to be represented as a JavaScript number: %" PRId64, + value); + return MaybeLocal(); } - THROW_ERR_OUT_OF_RANGE( - isolate, - "Value is too large to be represented as a JavaScript number: %" PRId64, - value); - return MaybeLocal(); + return Number::New(isolate, value); } #define CHECK_ERROR_OR_THROW(isolate, db, expr, expected, ret) \