From 7d196682557ab191ea24cc6c604ff0860ee0506d Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sat, 1 Feb 2025 13:19:53 +0530 Subject: [PATCH] fix(ext/node): throw RangeError when sqlite INTEGER is too large (#27907) Signed-off-by: Divy Srivastava --- ext/node/ops/sqlite/mod.rs | 3 +++ ext/node/ops/sqlite/statement.rs | 15 ++++++++++----- tests/unit_node/sqlite_test.ts | 10 ++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ext/node/ops/sqlite/mod.rs b/ext/node/ops/sqlite/mod.rs index 7a5af66a14..29c246dae5 100644 --- a/ext/node/ops/sqlite/mod.rs +++ b/ext/node/ops/sqlite/mod.rs @@ -38,4 +38,7 @@ pub enum SqliteError { #[class(generic)] #[error("Invalid constructor")] InvalidConstructor, + #[class(range)] + #[error("The value of column {0} is too large to be represented as a JavaScript number: {1}")] + NumberTooLarge(i32, i64), } diff --git a/ext/node/ops/sqlite/statement.rs b/ext/node/ops/sqlite/statement.rs index 8f96d5131a..1fa8ade599 100644 --- a/ext/node/ops/sqlite/statement.rs +++ b/ext/node/ops/sqlite/statement.rs @@ -12,6 +12,9 @@ use serde::Serialize; use super::SqliteError; +// ECMA-262, 15th edition, 21.1.2.6. Number.MAX_SAFE_INTEGER (2^53-1) +const MAX_SAFE_JS_INTEGER: i64 = 9007199254740991; + #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct RunStatementResult { @@ -122,17 +125,19 @@ impl StatementSync { &self, index: i32, scope: &mut v8::HandleScope<'a>, - ) -> v8::Local<'a, v8::Value> { + ) -> Result, SqliteError> { // SAFETY: `self.inner` is a valid pointer to a sqlite3_stmt // as it lives as long as the StatementSync instance. unsafe { - match ffi::sqlite3_column_type(self.inner, index) { + Ok(match ffi::sqlite3_column_type(self.inner, index) { ffi::SQLITE_INTEGER => { let value = ffi::sqlite3_column_int64(self.inner, index); if self.use_big_ints.get() { v8::BigInt::new_from_i64(scope, value).into() - } else { + } else if value.abs() <= MAX_SAFE_JS_INTEGER { v8::Integer::new(scope, value as _).into() + } else { + return Err(SqliteError::NumberTooLarge(index, value)); } } ffi::SQLITE_FLOAT => { @@ -162,7 +167,7 @@ impl StatementSync { } ffi::SQLITE_NULL => v8::null(scope).into(), _ => v8::undefined(scope).into(), - } + }) } } @@ -183,7 +188,7 @@ impl StatementSync { let mut values = Vec::with_capacity(num_cols); for (index, name) in iter { - let value = self.column_value(index, scope); + let value = self.column_value(index, scope)?; let name = v8::String::new_from_utf8(scope, name, v8::NewStringType::Normal) .unwrap() diff --git a/tests/unit_node/sqlite_test.ts b/tests/unit_node/sqlite_test.ts index 04522083af..43b6914f3c 100644 --- a/tests/unit_node/sqlite_test.ts +++ b/tests/unit_node/sqlite_test.ts @@ -73,6 +73,16 @@ Deno.test("[node/sqlite] StatementSync read bigints are supported", () => { assertEquals(stmt.get(), { key: 1n, __proto__: null }); }); +Deno.test("[node/sqlite] StatementSync integer too large", () => { + const db = new DatabaseSync(":memory:"); + db.exec("CREATE TABLE data(key INTEGER PRIMARY KEY);"); + db.prepare("INSERT INTO data (key) VALUES (?)").run( + Number.MAX_SAFE_INTEGER + 1, + ); + + assertThrows(() => db.prepare("SELECT * FROM data").get()); +}); + Deno.test("[node/sqlite] StatementSync blob are Uint8Array", () => { const db = new DatabaseSync(":memory:"); const obj = db.prepare("select cast('test' as blob)").all();