From 16c1dd26b7079e9daa43549f175d86c7991b2cc3 Mon Sep 17 00:00:00 2001 From: Ophir Lojkine Date: Fri, 14 Aug 2026 14:47:35 +0000 Subject: [PATCH] fix: restore WASM stack in Database.exec to prevent stack leak exec() allocates pzTail with stackAlloc(4) but never pairs it with stackSave()/stackRestore(), permanently consuming 16 bytes of the WASM stack per call (success or failure). PR #606 removed the stack save/restore when moving the SQL string to the heap but left the pzTail allocation in place. With the 5MB stack this exhausts the module after ~327k exec() calls, after which the module is corrupted. Fixes #630 --- src/api.js | 2 ++ test/test_issue630.js | 48 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 test/test_issue630.js diff --git a/src/api.js b/src/api.js index 829f2bfe..126baf1c 100644 --- a/src/api.js +++ b/src/api.js @@ -946,6 +946,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { if (!this.db) { throw "Database closed"; } + var stack = stackSave(); var stmt = null; var originalSqlPtr = null; var currentSqlPtr = null; @@ -993,6 +994,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { throw errCaught; } finally { if (originalSqlPtr) _free(originalSqlPtr); + stackRestore(stack); } }; diff --git a/test/test_issue630.js b/test/test_issue630.js new file mode 100644 index 00000000..c0f98385 --- /dev/null +++ b/test/test_issue630.js @@ -0,0 +1,48 @@ + +exports.test = function(sql, assert) { + "use strict"; + var db = new sql.Database(); + db.run("CREATE TABLE t (x); INSERT INTO t VALUES (1);"); + + var before = sql.stackSave(); + for (var i = 0; i < 1000; i++) { + db.exec("SELECT x FROM t"); + } + assert.strictEqual( + before - sql.stackSave(), + 0, + "exec() should not leak stack memory after repeated successful calls" + ); + + for (var j = 0; j < 100; j++) { + assert.throws( + function () { db.exec("SELECT * FROM does_not_exist"); }, + "no such table: does_not_exist", + "exec() should throw for a failing query" + ); + } + assert.strictEqual( + before - sql.stackSave(), + 0, + "exec() should not leak stack memory after repeated failing calls" + ); + + // Close the database and all associated statements + db.close(); +}; + +if (module == require.main) { + const target_file = process.argv[2]; + const sql_loader = require('./load_sql_lib'); + sql_loader(target_file).then((sql)=>{ + require('test').run({ + 'test issue630': function(assert){ + exports.test(sql, assert); + } + }); + }) + .catch((e)=>{ + console.error(e); + assert.fail(e); + }); +}