Skip to content

Commit 01f2928

Browse files
committed
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
1 parent d512d2d commit 01f2928

2 files changed

Lines changed: 42 additions & 16 deletions

File tree

src/node_sqlite.cc

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,24 @@ inline MaybeLocal<String> Utf8StringMaybeOneByte(Isolate* isolate,
7979
isolate, input.data(), NewStringType::kNormal, len);
8080
}
8181

82+
inline MaybeLocal<Value> IntegerToValue(Isolate* isolate,
83+
sqlite3_int64 value,
84+
bool use_big_ints) {
85+
if (use_big_ints) {
86+
return BigInt::New(isolate, value);
87+
}
88+
89+
if (value >= -kMaxSafeJsInteger && value <= kMaxSafeJsInteger) {
90+
return Number::New(isolate, value);
91+
}
92+
93+
THROW_ERR_OUT_OF_RANGE(
94+
isolate,
95+
"Value is too large to be represented as a JavaScript number: %" PRId64,
96+
value);
97+
return MaybeLocal<Value>();
98+
}
99+
82100
#define CHECK_ERROR_OR_THROW(isolate, db, expr, expected, ret) \
83101
do { \
84102
int r_ = (expr); \
@@ -101,16 +119,7 @@ inline MaybeLocal<String> Utf8StringMaybeOneByte(Isolate* isolate,
101119
switch (sqlite3_##from##_type(__VA_ARGS__)) { \
102120
case SQLITE_INTEGER: { \
103121
sqlite3_int64 val = sqlite3_##from##_int64(__VA_ARGS__); \
104-
if ((use_big_int_args)) { \
105-
(result) = BigInt::New((isolate), val); \
106-
} else if (std::abs(val) <= kMaxSafeJsInteger) { \
107-
(result) = Number::New((isolate), val); \
108-
} else { \
109-
THROW_ERR_OUT_OF_RANGE((isolate), \
110-
"Value is too large to be represented as a " \
111-
"JavaScript number: %" PRId64, \
112-
val); \
113-
} \
122+
(result) = IntegerToValue((isolate), val, (use_big_int_args)); \
114123
break; \
115124
} \
116125
case SQLITE_FLOAT: { \
@@ -3018,12 +3027,10 @@ MaybeLocal<Object> StatementExecutionHelper::Run(Environment* env,
30183027
Local<Value> last_insert_rowid_val;
30193028
Local<Value> changes_val;
30203029

3021-
if (use_big_ints) {
3022-
last_insert_rowid_val = BigInt::New(isolate, last_insert_rowid);
3023-
changes_val = BigInt::New(isolate, changes);
3024-
} else {
3025-
last_insert_rowid_val = Number::New(isolate, last_insert_rowid);
3026-
changes_val = Number::New(isolate, changes);
3030+
if (!IntegerToValue(isolate, last_insert_rowid, use_big_ints)
3031+
.ToLocal(&last_insert_rowid_val) ||
3032+
!IntegerToValue(isolate, changes, use_big_ints).ToLocal(&changes_val)) {
3033+
return MaybeLocal<Object>();
30273034
}
30283035

30293036
auto run_result_template = env->sqlite_run_result_template();

test/parallel/test-sqlite-statement-sync.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,25 @@ suite('StatementSync.prototype.setReadBigInts()', () => {
556556
});
557557
});
558558

559+
test('BigInt is required for reading large last insert row IDs', (t) => {
560+
using db = new DatabaseSync(':memory:');
561+
db.exec('CREATE TABLE data(key INTEGER PRIMARY KEY) STRICT');
562+
const insert = db.prepare('INSERT INTO data VALUES (?)');
563+
564+
t.assert.throws(() => {
565+
insert.run(9007199254740993n);
566+
}, {
567+
code: 'ERR_OUT_OF_RANGE',
568+
message: /^Value is too large to be represented as a JavaScript number: 9007199254740993$/,
569+
});
570+
571+
insert.setReadBigInts(true);
572+
t.assert.deepStrictEqual(insert.run(9007199254740995n), {
573+
changes: 1n,
574+
lastInsertRowid: 9007199254740995n,
575+
});
576+
});
577+
559578
test('throws if the statement is already finalized', (t) => {
560579
using db = new DatabaseSync(':memory:');
561580
const stmt = db.prepare('CREATE TABLE storage(key TEXT, val TEXT)');

0 commit comments

Comments
 (0)