sqlite: reject deserialize() while in a callback - #64796

Merged
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close
Aug 9, 2026
Merged

sqlite: reject deserialize() while in a callback#64796
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close

Conversation

@trivikr

@trivikrtrivikr commented Jul 28, 2026

Copy link
Copy Markdown
Member

Refs: #64795

deserialize() could be called from a user-defined function invoked during statement execution, tearing down the database connection while sqlite3_step() was still using it. Reuse the existing callback depth check to throw ERR_INVALID_STATE instead, matching the guard already in place for close().


Assisted-by: codex:gpt-5.6-sol

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/sqlite

@nodejs-github-botnodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. sqlite Issues and PRs related to the SQLite subsystem. labels Jul 28, 2026
@codecov

codecovBot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.33%. Comparing base (bf2f995) to head (b2b7405).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #64796 +/- ##
==========================================
- Coverage 90.33% 90.33% -0.01% 
==========================================
Files 760 760 Lines 248522 248523 +1 Branches 46904 46906 +2 ==========================================
- Hits 224513 224511 -2 - Misses 15444 15451 +7 + Partials 8565 8561 -4 
Files with missing linesCoverage Δ
src/node_sqlite.cc81.23% <100.00%> (+<0.01%)⬆️

... and 29 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@trivikrtrivikr added the request-ci Add this label to start a Jenkins CI on a PR. label Jul 28, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c11abfd to 4af37c6CompareJuly 31, 2026 04:28
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from 4af37c6 to c64b289CompareAugust 3, 2026 19:58
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 3 times, most recently from 46139cd to e6c5c8cCompareAugust 5, 2026 02:22

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guard itself looks right to me: IsInCallback() is the correct predicate, since the crash needs a live sqlite3_step() frame, which is only reachable from a callback, and every step-reentrant entry point (xFunc, xStepBase/xValueBase, the authorizer, sqlite3changeset_apply) already takes CallbackDepthGuard. Placing it before FinalizeStatements() also means a rejected call leaves no partial state. Three non-blocking notes below, plus one on the docs.

doc/api/sqlite.md: the new throwing condition isn't documented. The database.deserialize() section only mentions that existing statements are finalized first; it'd help to state that the method throws ERR_INVALID_STATE when called from a user-defined function, aggregate, authorizer, or changeset filter/conflict callback. The same is missing for database.close() from #64743 — might be worth adding both here.

Comment threadsrc/node_sqlite.cc
Comment threadtest/parallel/test-sqlite-serialize.js Outdated
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from e6c5c8c to 19c3939CompareAugust 6, 2026 18:10
@trivikrtrivikr changed the title sqlite: prevent reentrant statement finalizationsqlite: reject deserialize() while in a callbackAug 6, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 2 times, most recently from ae8a08a to 0081aa8CompareAugust 7, 2026 03:34

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround on the commit message and the test rename — both look good, and keeping the test with its deserialize siblings is a fair call.

The C++ guard is unchanged and still correct. I built this branch and confirmed the #64795 repro now throws instead of crashing, that coverage extends past the tested UDF case to aggregate and authorizer callbacks, and that there are no false positives across connections (deserializing an unrelated database from another database's callback still works). The remaining comments are all on the doc commit, plus one on the Fixes: line.

One more docs point that isn't in the diff: database.close() (line 291) has carried this same callback restriction since 5cef767, but its docs still say only "An exception is thrown if the database is not open." Documenting the restriction for deserialize() while leaving close() undocumented is an odd asymmetry — probably worth a one-line addition there while you're in this section.

Comment threaddoc/api/sqlite.md Outdated
Comment threaddoc/api/sqlite.md Outdated
Comment threadsrc/node_sqlite.cc
@TrevorBurnham

Copy link
Copy Markdown
Contributor

Looks ready to 🚢 to me.

One non-blocking note: database.close() is guarded by the same IsInCallback() check as deserialize(), and throws ERR_INVALID_STATE under the same conditions, but its docs don't reflect that. I'd suggest copying the sentence you added to the docs for deserialize() over to the docs for database.close().

@trivikr

Copy link
Copy Markdown
MemberAuthor

The database.close() doc update us posted at #65090

I'll mark it ready for review post rebase after this PR is merged, as they share a reference link.

Qard
Qard approved these changes Aug 9, 2026
@github-actionsgithub-actionsBot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 9, 2026
@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: nodejs#64796
Refs: nodejs#64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c32ea43 to b2b7405CompareAugust 9, 2026 23:27
@trivikr
trivikr merged commit b2b7405 into nodejs:mainAug 9, 2026
19 checks passed
@trivikr

Copy link
Copy Markdown
MemberAuthor

Landed in b2b7405

@trivikr
trivikr deleted the sqlite-reject-reentrant-close branch August 9, 2026 23:28
aduh95 pushed a commit that referenced this pull request Aug 13, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
aduh95 pushed a commit that referenced this pull request Aug 25, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++Issues and PRs that require attention from people who are familiar with C++.needs-ciPRs that need a full CI run.sqliteIssues and PRs related to the SQLite subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@trivikr@nodejs-github-bot@TrevorBurnham@Qard
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

sqlite: reject deserialize() while in a callback - #64796

Merged
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close
Aug 9, 2026
Merged

sqlite: reject deserialize() while in a callback#64796
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close

Conversation

@trivikr

@trivikrtrivikr commented Jul 28, 2026

Copy link
Copy Markdown
Member

Refs: #64795

deserialize() could be called from a user-defined function invoked during statement execution, tearing down the database connection while sqlite3_step() was still using it. Reuse the existing callback depth check to throw ERR_INVALID_STATE instead, matching the guard already in place for close().


Assisted-by: codex:gpt-5.6-sol

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/sqlite

@nodejs-github-botnodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. sqlite Issues and PRs related to the SQLite subsystem. labels Jul 28, 2026
@codecov

codecovBot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.33%. Comparing base (bf2f995) to head (b2b7405).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #64796 +/- ##
==========================================
- Coverage 90.33% 90.33% -0.01% 
==========================================
Files 760 760 Lines 248522 248523 +1 Branches 46904 46906 +2 ==========================================
- Hits 224513 224511 -2 - Misses 15444 15451 +7 + Partials 8565 8561 -4 
Files with missing linesCoverage Δ
src/node_sqlite.cc81.23% <100.00%> (+<0.01%)⬆️

... and 29 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@trivikrtrivikr added the request-ci Add this label to start a Jenkins CI on a PR. label Jul 28, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c11abfd to 4af37c6CompareJuly 31, 2026 04:28
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from 4af37c6 to c64b289CompareAugust 3, 2026 19:58
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 3 times, most recently from 46139cd to e6c5c8cCompareAugust 5, 2026 02:22

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guard itself looks right to me: IsInCallback() is the correct predicate, since the crash needs a live sqlite3_step() frame, which is only reachable from a callback, and every step-reentrant entry point (xFunc, xStepBase/xValueBase, the authorizer, sqlite3changeset_apply) already takes CallbackDepthGuard. Placing it before FinalizeStatements() also means a rejected call leaves no partial state. Three non-blocking notes below, plus one on the docs.

doc/api/sqlite.md: the new throwing condition isn't documented. The database.deserialize() section only mentions that existing statements are finalized first; it'd help to state that the method throws ERR_INVALID_STATE when called from a user-defined function, aggregate, authorizer, or changeset filter/conflict callback. The same is missing for database.close() from #64743 — might be worth adding both here.

Comment threadsrc/node_sqlite.cc
Comment threadtest/parallel/test-sqlite-serialize.js Outdated
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from e6c5c8c to 19c3939CompareAugust 6, 2026 18:10
@trivikrtrivikr changed the title sqlite: prevent reentrant statement finalizationsqlite: reject deserialize() while in a callbackAug 6, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 2 times, most recently from ae8a08a to 0081aa8CompareAugust 7, 2026 03:34

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround on the commit message and the test rename — both look good, and keeping the test with its deserialize siblings is a fair call.

The C++ guard is unchanged and still correct. I built this branch and confirmed the #64795 repro now throws instead of crashing, that coverage extends past the tested UDF case to aggregate and authorizer callbacks, and that there are no false positives across connections (deserializing an unrelated database from another database's callback still works). The remaining comments are all on the doc commit, plus one on the Fixes: line.

One more docs point that isn't in the diff: database.close() (line 291) has carried this same callback restriction since 5cef767, but its docs still say only "An exception is thrown if the database is not open." Documenting the restriction for deserialize() while leaving close() undocumented is an odd asymmetry — probably worth a one-line addition there while you're in this section.

Comment threaddoc/api/sqlite.md Outdated
Comment threaddoc/api/sqlite.md Outdated
Comment threadsrc/node_sqlite.cc
@TrevorBurnham

Copy link
Copy Markdown
Contributor

Looks ready to 🚢 to me.

One non-blocking note: database.close() is guarded by the same IsInCallback() check as deserialize(), and throws ERR_INVALID_STATE under the same conditions, but its docs don't reflect that. I'd suggest copying the sentence you added to the docs for deserialize() over to the docs for database.close().

@trivikr

Copy link
Copy Markdown
MemberAuthor

The database.close() doc update us posted at #65090

I'll mark it ready for review post rebase after this PR is merged, as they share a reference link.

Qard
Qard approved these changes Aug 9, 2026
@github-actionsgithub-actionsBot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 9, 2026
@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: nodejs#64796
Refs: nodejs#64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c32ea43 to b2b7405CompareAugust 9, 2026 23:27
@trivikr
trivikr merged commit b2b7405 into nodejs:mainAug 9, 2026
19 checks passed
@trivikr

Copy link
Copy Markdown
MemberAuthor

Landed in b2b7405

@trivikr
trivikr deleted the sqlite-reject-reentrant-close branch August 9, 2026 23:28
aduh95 pushed a commit that referenced this pull request Aug 13, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
aduh95 pushed a commit that referenced this pull request Aug 25, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++Issues and PRs that require attention from people who are familiar with C++.needs-ciPRs that need a full CI run.sqliteIssues and PRs related to the SQLite subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@trivikr@nodejs-github-bot@TrevorBurnham@Qard
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

sqlite: reject deserialize() while in a callback - #64796

Merged
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close
Aug 9, 2026
Merged

sqlite: reject deserialize() while in a callback#64796
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close

Conversation

@trivikr

@trivikrtrivikr commented Jul 28, 2026

Copy link
Copy Markdown
Member

Refs: #64795

deserialize() could be called from a user-defined function invoked during statement execution, tearing down the database connection while sqlite3_step() was still using it. Reuse the existing callback depth check to throw ERR_INVALID_STATE instead, matching the guard already in place for close().


Assisted-by: codex:gpt-5.6-sol

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/sqlite

@nodejs-github-botnodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. sqlite Issues and PRs related to the SQLite subsystem. labels Jul 28, 2026
@codecov

codecovBot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.33%. Comparing base (bf2f995) to head (b2b7405).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #64796 +/- ##
==========================================
- Coverage 90.33% 90.33% -0.01% 
==========================================
Files 760 760 Lines 248522 248523 +1 Branches 46904 46906 +2 ==========================================
- Hits 224513 224511 -2 - Misses 15444 15451 +7 + Partials 8565 8561 -4 
Files with missing linesCoverage Δ
src/node_sqlite.cc81.23% <100.00%> (+<0.01%)⬆️

... and 29 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@trivikrtrivikr added the request-ci Add this label to start a Jenkins CI on a PR. label Jul 28, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c11abfd to 4af37c6CompareJuly 31, 2026 04:28
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from 4af37c6 to c64b289CompareAugust 3, 2026 19:58
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 3 times, most recently from 46139cd to e6c5c8cCompareAugust 5, 2026 02:22

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guard itself looks right to me: IsInCallback() is the correct predicate, since the crash needs a live sqlite3_step() frame, which is only reachable from a callback, and every step-reentrant entry point (xFunc, xStepBase/xValueBase, the authorizer, sqlite3changeset_apply) already takes CallbackDepthGuard. Placing it before FinalizeStatements() also means a rejected call leaves no partial state. Three non-blocking notes below, plus one on the docs.

doc/api/sqlite.md: the new throwing condition isn't documented. The database.deserialize() section only mentions that existing statements are finalized first; it'd help to state that the method throws ERR_INVALID_STATE when called from a user-defined function, aggregate, authorizer, or changeset filter/conflict callback. The same is missing for database.close() from #64743 — might be worth adding both here.

Comment threadsrc/node_sqlite.cc
Comment threadtest/parallel/test-sqlite-serialize.js Outdated
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from e6c5c8c to 19c3939CompareAugust 6, 2026 18:10
@trivikrtrivikr changed the title sqlite: prevent reentrant statement finalizationsqlite: reject deserialize() while in a callbackAug 6, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 2 times, most recently from ae8a08a to 0081aa8CompareAugust 7, 2026 03:34

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround on the commit message and the test rename — both look good, and keeping the test with its deserialize siblings is a fair call.

The C++ guard is unchanged and still correct. I built this branch and confirmed the #64795 repro now throws instead of crashing, that coverage extends past the tested UDF case to aggregate and authorizer callbacks, and that there are no false positives across connections (deserializing an unrelated database from another database's callback still works). The remaining comments are all on the doc commit, plus one on the Fixes: line.

One more docs point that isn't in the diff: database.close() (line 291) has carried this same callback restriction since 5cef767, but its docs still say only "An exception is thrown if the database is not open." Documenting the restriction for deserialize() while leaving close() undocumented is an odd asymmetry — probably worth a one-line addition there while you're in this section.

Comment threaddoc/api/sqlite.md Outdated
Comment threaddoc/api/sqlite.md Outdated
Comment threadsrc/node_sqlite.cc
@TrevorBurnham

Copy link
Copy Markdown
Contributor

Looks ready to 🚢 to me.

One non-blocking note: database.close() is guarded by the same IsInCallback() check as deserialize(), and throws ERR_INVALID_STATE under the same conditions, but its docs don't reflect that. I'd suggest copying the sentence you added to the docs for deserialize() over to the docs for database.close().

@trivikr

Copy link
Copy Markdown
MemberAuthor

The database.close() doc update us posted at #65090

I'll mark it ready for review post rebase after this PR is merged, as they share a reference link.

Qard
Qard approved these changes Aug 9, 2026
@github-actionsgithub-actionsBot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 9, 2026
@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: nodejs#64796
Refs: nodejs#64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c32ea43 to b2b7405CompareAugust 9, 2026 23:27
@trivikr
trivikr merged commit b2b7405 into nodejs:mainAug 9, 2026
19 checks passed
@trivikr

Copy link
Copy Markdown
MemberAuthor

Landed in b2b7405

@trivikr
trivikr deleted the sqlite-reject-reentrant-close branch August 9, 2026 23:28
aduh95 pushed a commit that referenced this pull request Aug 13, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
aduh95 pushed a commit that referenced this pull request Aug 25, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++Issues and PRs that require attention from people who are familiar with C++.needs-ciPRs that need a full CI run.sqliteIssues and PRs related to the SQLite subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@trivikr@nodejs-github-bot@TrevorBurnham@Qard
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

sqlite: reject deserialize() while in a callback - #64796

Merged
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close
Aug 9, 2026
Merged

sqlite: reject deserialize() while in a callback#64796
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close

Conversation

@trivikr

@trivikrtrivikr commented Jul 28, 2026

Copy link
Copy Markdown
Member

Refs: #64795

deserialize() could be called from a user-defined function invoked during statement execution, tearing down the database connection while sqlite3_step() was still using it. Reuse the existing callback depth check to throw ERR_INVALID_STATE instead, matching the guard already in place for close().


Assisted-by: codex:gpt-5.6-sol

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/sqlite

@nodejs-github-botnodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. sqlite Issues and PRs related to the SQLite subsystem. labels Jul 28, 2026
@codecov

codecovBot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.33%. Comparing base (bf2f995) to head (b2b7405).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #64796 +/- ##
==========================================
- Coverage 90.33% 90.33% -0.01% 
==========================================
Files 760 760 Lines 248522 248523 +1 Branches 46904 46906 +2 ==========================================
- Hits 224513 224511 -2 - Misses 15444 15451 +7 + Partials 8565 8561 -4 
Files with missing linesCoverage Δ
src/node_sqlite.cc81.23% <100.00%> (+<0.01%)⬆️

... and 29 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@trivikrtrivikr added the request-ci Add this label to start a Jenkins CI on a PR. label Jul 28, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c11abfd to 4af37c6CompareJuly 31, 2026 04:28
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from 4af37c6 to c64b289CompareAugust 3, 2026 19:58
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 3 times, most recently from 46139cd to e6c5c8cCompareAugust 5, 2026 02:22

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guard itself looks right to me: IsInCallback() is the correct predicate, since the crash needs a live sqlite3_step() frame, which is only reachable from a callback, and every step-reentrant entry point (xFunc, xStepBase/xValueBase, the authorizer, sqlite3changeset_apply) already takes CallbackDepthGuard. Placing it before FinalizeStatements() also means a rejected call leaves no partial state. Three non-blocking notes below, plus one on the docs.

doc/api/sqlite.md: the new throwing condition isn't documented. The database.deserialize() section only mentions that existing statements are finalized first; it'd help to state that the method throws ERR_INVALID_STATE when called from a user-defined function, aggregate, authorizer, or changeset filter/conflict callback. The same is missing for database.close() from #64743 — might be worth adding both here.

Comment threadsrc/node_sqlite.cc
Comment threadtest/parallel/test-sqlite-serialize.js Outdated
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from e6c5c8c to 19c3939CompareAugust 6, 2026 18:10
@trivikrtrivikr changed the title sqlite: prevent reentrant statement finalizationsqlite: reject deserialize() while in a callbackAug 6, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 2 times, most recently from ae8a08a to 0081aa8CompareAugust 7, 2026 03:34

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround on the commit message and the test rename — both look good, and keeping the test with its deserialize siblings is a fair call.

The C++ guard is unchanged and still correct. I built this branch and confirmed the #64795 repro now throws instead of crashing, that coverage extends past the tested UDF case to aggregate and authorizer callbacks, and that there are no false positives across connections (deserializing an unrelated database from another database's callback still works). The remaining comments are all on the doc commit, plus one on the Fixes: line.

One more docs point that isn't in the diff: database.close() (line 291) has carried this same callback restriction since 5cef767, but its docs still say only "An exception is thrown if the database is not open." Documenting the restriction for deserialize() while leaving close() undocumented is an odd asymmetry — probably worth a one-line addition there while you're in this section.

Comment threaddoc/api/sqlite.md Outdated
Comment threaddoc/api/sqlite.md Outdated
Comment threadsrc/node_sqlite.cc
@TrevorBurnham

Copy link
Copy Markdown
Contributor

Looks ready to 🚢 to me.

One non-blocking note: database.close() is guarded by the same IsInCallback() check as deserialize(), and throws ERR_INVALID_STATE under the same conditions, but its docs don't reflect that. I'd suggest copying the sentence you added to the docs for deserialize() over to the docs for database.close().

@trivikr

Copy link
Copy Markdown
MemberAuthor

The database.close() doc update us posted at #65090

I'll mark it ready for review post rebase after this PR is merged, as they share a reference link.

Qard
Qard approved these changes Aug 9, 2026
@github-actionsgithub-actionsBot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 9, 2026
@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: nodejs#64796
Refs: nodejs#64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c32ea43 to b2b7405CompareAugust 9, 2026 23:27
@trivikr
trivikr merged commit b2b7405 into nodejs:mainAug 9, 2026
19 checks passed
@trivikr

Copy link
Copy Markdown
MemberAuthor

Landed in b2b7405

@trivikr
trivikr deleted the sqlite-reject-reentrant-close branch August 9, 2026 23:28
aduh95 pushed a commit that referenced this pull request Aug 13, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
aduh95 pushed a commit that referenced this pull request Aug 25, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++Issues and PRs that require attention from people who are familiar with C++.needs-ciPRs that need a full CI run.sqliteIssues and PRs related to the SQLite subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@trivikr@nodejs-github-bot@TrevorBurnham@Qard
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

sqlite: reject deserialize() while in a callback - #64796

Merged
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close
Aug 9, 2026
Merged

sqlite: reject deserialize() while in a callback#64796
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close

Conversation

@trivikr

@trivikrtrivikr commented Jul 28, 2026

Copy link
Copy Markdown
Member

Refs: #64795

deserialize() could be called from a user-defined function invoked during statement execution, tearing down the database connection while sqlite3_step() was still using it. Reuse the existing callback depth check to throw ERR_INVALID_STATE instead, matching the guard already in place for close().


Assisted-by: codex:gpt-5.6-sol

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/sqlite

@nodejs-github-botnodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. sqlite Issues and PRs related to the SQLite subsystem. labels Jul 28, 2026
@codecov

codecovBot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.33%. Comparing base (bf2f995) to head (b2b7405).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #64796 +/- ##
==========================================
- Coverage 90.33% 90.33% -0.01% 
==========================================
Files 760 760 Lines 248522 248523 +1 Branches 46904 46906 +2 ==========================================
- Hits 224513 224511 -2 - Misses 15444 15451 +7 + Partials 8565 8561 -4 
Files with missing linesCoverage Δ
src/node_sqlite.cc81.23% <100.00%> (+<0.01%)⬆️

... and 29 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@trivikrtrivikr added the request-ci Add this label to start a Jenkins CI on a PR. label Jul 28, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c11abfd to 4af37c6CompareJuly 31, 2026 04:28
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from 4af37c6 to c64b289CompareAugust 3, 2026 19:58
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 3 times, most recently from 46139cd to e6c5c8cCompareAugust 5, 2026 02:22

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guard itself looks right to me: IsInCallback() is the correct predicate, since the crash needs a live sqlite3_step() frame, which is only reachable from a callback, and every step-reentrant entry point (xFunc, xStepBase/xValueBase, the authorizer, sqlite3changeset_apply) already takes CallbackDepthGuard. Placing it before FinalizeStatements() also means a rejected call leaves no partial state. Three non-blocking notes below, plus one on the docs.

doc/api/sqlite.md: the new throwing condition isn't documented. The database.deserialize() section only mentions that existing statements are finalized first; it'd help to state that the method throws ERR_INVALID_STATE when called from a user-defined function, aggregate, authorizer, or changeset filter/conflict callback. The same is missing for database.close() from #64743 — might be worth adding both here.

Comment threadsrc/node_sqlite.cc
Comment threadtest/parallel/test-sqlite-serialize.js Outdated
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from e6c5c8c to 19c3939CompareAugust 6, 2026 18:10
@trivikrtrivikr changed the title sqlite: prevent reentrant statement finalizationsqlite: reject deserialize() while in a callbackAug 6, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 2 times, most recently from ae8a08a to 0081aa8CompareAugust 7, 2026 03:34

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround on the commit message and the test rename — both look good, and keeping the test with its deserialize siblings is a fair call.

The C++ guard is unchanged and still correct. I built this branch and confirmed the #64795 repro now throws instead of crashing, that coverage extends past the tested UDF case to aggregate and authorizer callbacks, and that there are no false positives across connections (deserializing an unrelated database from another database's callback still works). The remaining comments are all on the doc commit, plus one on the Fixes: line.

One more docs point that isn't in the diff: database.close() (line 291) has carried this same callback restriction since 5cef767, but its docs still say only "An exception is thrown if the database is not open." Documenting the restriction for deserialize() while leaving close() undocumented is an odd asymmetry — probably worth a one-line addition there while you're in this section.

Comment threaddoc/api/sqlite.md Outdated
Comment threaddoc/api/sqlite.md Outdated
Comment threadsrc/node_sqlite.cc
@TrevorBurnham

Copy link
Copy Markdown
Contributor

Looks ready to 🚢 to me.

One non-blocking note: database.close() is guarded by the same IsInCallback() check as deserialize(), and throws ERR_INVALID_STATE under the same conditions, but its docs don't reflect that. I'd suggest copying the sentence you added to the docs for deserialize() over to the docs for database.close().

@trivikr

Copy link
Copy Markdown
MemberAuthor

The database.close() doc update us posted at #65090

I'll mark it ready for review post rebase after this PR is merged, as they share a reference link.

Qard
Qard approved these changes Aug 9, 2026
@github-actionsgithub-actionsBot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 9, 2026
@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: nodejs#64796
Refs: nodejs#64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c32ea43 to b2b7405CompareAugust 9, 2026 23:27
@trivikr
trivikr merged commit b2b7405 into nodejs:mainAug 9, 2026
19 checks passed
@trivikr

Copy link
Copy Markdown
MemberAuthor

Landed in b2b7405

@trivikr
trivikr deleted the sqlite-reject-reentrant-close branch August 9, 2026 23:28
aduh95 pushed a commit that referenced this pull request Aug 13, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
aduh95 pushed a commit that referenced this pull request Aug 25, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++Issues and PRs that require attention from people who are familiar with C++.needs-ciPRs that need a full CI run.sqliteIssues and PRs related to the SQLite subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@trivikr@nodejs-github-bot@TrevorBurnham@Qard
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

sqlite: reject deserialize() while in a callback - #64796

Merged
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close
Aug 9, 2026
Merged

sqlite: reject deserialize() while in a callback#64796
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close

Conversation

@trivikr

@trivikrtrivikr commented Jul 28, 2026

Copy link
Copy Markdown
Member

Refs: #64795

deserialize() could be called from a user-defined function invoked during statement execution, tearing down the database connection while sqlite3_step() was still using it. Reuse the existing callback depth check to throw ERR_INVALID_STATE instead, matching the guard already in place for close().


Assisted-by: codex:gpt-5.6-sol

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/sqlite

@nodejs-github-botnodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. sqlite Issues and PRs related to the SQLite subsystem. labels Jul 28, 2026
@codecov

codecovBot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.33%. Comparing base (bf2f995) to head (b2b7405).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #64796 +/- ##
==========================================
- Coverage 90.33% 90.33% -0.01% 
==========================================
Files 760 760 Lines 248522 248523 +1 Branches 46904 46906 +2 ==========================================
- Hits 224513 224511 -2 - Misses 15444 15451 +7 + Partials 8565 8561 -4 
Files with missing linesCoverage Δ
src/node_sqlite.cc81.23% <100.00%> (+<0.01%)⬆️

... and 29 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@trivikrtrivikr added the request-ci Add this label to start a Jenkins CI on a PR. label Jul 28, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c11abfd to 4af37c6CompareJuly 31, 2026 04:28
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from 4af37c6 to c64b289CompareAugust 3, 2026 19:58
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 3 times, most recently from 46139cd to e6c5c8cCompareAugust 5, 2026 02:22

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guard itself looks right to me: IsInCallback() is the correct predicate, since the crash needs a live sqlite3_step() frame, which is only reachable from a callback, and every step-reentrant entry point (xFunc, xStepBase/xValueBase, the authorizer, sqlite3changeset_apply) already takes CallbackDepthGuard. Placing it before FinalizeStatements() also means a rejected call leaves no partial state. Three non-blocking notes below, plus one on the docs.

doc/api/sqlite.md: the new throwing condition isn't documented. The database.deserialize() section only mentions that existing statements are finalized first; it'd help to state that the method throws ERR_INVALID_STATE when called from a user-defined function, aggregate, authorizer, or changeset filter/conflict callback. The same is missing for database.close() from #64743 — might be worth adding both here.

Comment threadsrc/node_sqlite.cc
Comment threadtest/parallel/test-sqlite-serialize.js Outdated
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from e6c5c8c to 19c3939CompareAugust 6, 2026 18:10
@trivikrtrivikr changed the title sqlite: prevent reentrant statement finalizationsqlite: reject deserialize() while in a callbackAug 6, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 2 times, most recently from ae8a08a to 0081aa8CompareAugust 7, 2026 03:34

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround on the commit message and the test rename — both look good, and keeping the test with its deserialize siblings is a fair call.

The C++ guard is unchanged and still correct. I built this branch and confirmed the #64795 repro now throws instead of crashing, that coverage extends past the tested UDF case to aggregate and authorizer callbacks, and that there are no false positives across connections (deserializing an unrelated database from another database's callback still works). The remaining comments are all on the doc commit, plus one on the Fixes: line.

One more docs point that isn't in the diff: database.close() (line 291) has carried this same callback restriction since 5cef767, but its docs still say only "An exception is thrown if the database is not open." Documenting the restriction for deserialize() while leaving close() undocumented is an odd asymmetry — probably worth a one-line addition there while you're in this section.

Comment threaddoc/api/sqlite.md Outdated
Comment threaddoc/api/sqlite.md Outdated
Comment threadsrc/node_sqlite.cc
@TrevorBurnham

Copy link
Copy Markdown
Contributor

Looks ready to 🚢 to me.

One non-blocking note: database.close() is guarded by the same IsInCallback() check as deserialize(), and throws ERR_INVALID_STATE under the same conditions, but its docs don't reflect that. I'd suggest copying the sentence you added to the docs for deserialize() over to the docs for database.close().

@trivikr

Copy link
Copy Markdown
MemberAuthor

The database.close() doc update us posted at #65090

I'll mark it ready for review post rebase after this PR is merged, as they share a reference link.

Qard
Qard approved these changes Aug 9, 2026
@github-actionsgithub-actionsBot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 9, 2026
@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: nodejs#64796
Refs: nodejs#64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c32ea43 to b2b7405CompareAugust 9, 2026 23:27
@trivikr
trivikr merged commit b2b7405 into nodejs:mainAug 9, 2026
19 checks passed
@trivikr

Copy link
Copy Markdown
MemberAuthor

Landed in b2b7405

@trivikr
trivikr deleted the sqlite-reject-reentrant-close branch August 9, 2026 23:28
aduh95 pushed a commit that referenced this pull request Aug 13, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
aduh95 pushed a commit that referenced this pull request Aug 25, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++Issues and PRs that require attention from people who are familiar with C++.needs-ciPRs that need a full CI run.sqliteIssues and PRs related to the SQLite subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@trivikr@nodejs-github-bot@TrevorBurnham@Qard
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

sqlite: reject deserialize() while in a callback - #64796

Merged
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close
Aug 9, 2026
Merged

sqlite: reject deserialize() while in a callback#64796
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close

Conversation

@trivikr

@trivikrtrivikr commented Jul 28, 2026

Copy link
Copy Markdown
Member

Refs: #64795

deserialize() could be called from a user-defined function invoked during statement execution, tearing down the database connection while sqlite3_step() was still using it. Reuse the existing callback depth check to throw ERR_INVALID_STATE instead, matching the guard already in place for close().


Assisted-by: codex:gpt-5.6-sol

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/sqlite

@nodejs-github-botnodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. sqlite Issues and PRs related to the SQLite subsystem. labels Jul 28, 2026
@codecov

codecovBot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.33%. Comparing base (bf2f995) to head (b2b7405).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #64796 +/- ##
==========================================
- Coverage 90.33% 90.33% -0.01% 
==========================================
Files 760 760 Lines 248522 248523 +1 Branches 46904 46906 +2 ==========================================
- Hits 224513 224511 -2 - Misses 15444 15451 +7 + Partials 8565 8561 -4 
Files with missing linesCoverage Δ
src/node_sqlite.cc81.23% <100.00%> (+<0.01%)⬆️

... and 29 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@trivikrtrivikr added the request-ci Add this label to start a Jenkins CI on a PR. label Jul 28, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c11abfd to 4af37c6CompareJuly 31, 2026 04:28
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from 4af37c6 to c64b289CompareAugust 3, 2026 19:58
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 3 times, most recently from 46139cd to e6c5c8cCompareAugust 5, 2026 02:22

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guard itself looks right to me: IsInCallback() is the correct predicate, since the crash needs a live sqlite3_step() frame, which is only reachable from a callback, and every step-reentrant entry point (xFunc, xStepBase/xValueBase, the authorizer, sqlite3changeset_apply) already takes CallbackDepthGuard. Placing it before FinalizeStatements() also means a rejected call leaves no partial state. Three non-blocking notes below, plus one on the docs.

doc/api/sqlite.md: the new throwing condition isn't documented. The database.deserialize() section only mentions that existing statements are finalized first; it'd help to state that the method throws ERR_INVALID_STATE when called from a user-defined function, aggregate, authorizer, or changeset filter/conflict callback. The same is missing for database.close() from #64743 — might be worth adding both here.

Comment threadsrc/node_sqlite.cc
Comment threadtest/parallel/test-sqlite-serialize.js Outdated
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from e6c5c8c to 19c3939CompareAugust 6, 2026 18:10
@trivikrtrivikr changed the title sqlite: prevent reentrant statement finalizationsqlite: reject deserialize() while in a callbackAug 6, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 2 times, most recently from ae8a08a to 0081aa8CompareAugust 7, 2026 03:34

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround on the commit message and the test rename — both look good, and keeping the test with its deserialize siblings is a fair call.

The C++ guard is unchanged and still correct. I built this branch and confirmed the #64795 repro now throws instead of crashing, that coverage extends past the tested UDF case to aggregate and authorizer callbacks, and that there are no false positives across connections (deserializing an unrelated database from another database's callback still works). The remaining comments are all on the doc commit, plus one on the Fixes: line.

One more docs point that isn't in the diff: database.close() (line 291) has carried this same callback restriction since 5cef767, but its docs still say only "An exception is thrown if the database is not open." Documenting the restriction for deserialize() while leaving close() undocumented is an odd asymmetry — probably worth a one-line addition there while you're in this section.

Comment threaddoc/api/sqlite.md Outdated
Comment threaddoc/api/sqlite.md Outdated
Comment threadsrc/node_sqlite.cc
@TrevorBurnham

Copy link
Copy Markdown
Contributor

Looks ready to 🚢 to me.

One non-blocking note: database.close() is guarded by the same IsInCallback() check as deserialize(), and throws ERR_INVALID_STATE under the same conditions, but its docs don't reflect that. I'd suggest copying the sentence you added to the docs for deserialize() over to the docs for database.close().

@trivikr

Copy link
Copy Markdown
MemberAuthor

The database.close() doc update us posted at #65090

I'll mark it ready for review post rebase after this PR is merged, as they share a reference link.

Qard
Qard approved these changes Aug 9, 2026
@github-actionsgithub-actionsBot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 9, 2026
@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: nodejs#64796
Refs: nodejs#64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c32ea43 to b2b7405CompareAugust 9, 2026 23:27
@trivikr
trivikr merged commit b2b7405 into nodejs:mainAug 9, 2026
19 checks passed
@trivikr

Copy link
Copy Markdown
MemberAuthor

Landed in b2b7405

@trivikr
trivikr deleted the sqlite-reject-reentrant-close branch August 9, 2026 23:28
aduh95 pushed a commit that referenced this pull request Aug 13, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
aduh95 pushed a commit that referenced this pull request Aug 25, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++Issues and PRs that require attention from people who are familiar with C++.needs-ciPRs that need a full CI run.sqliteIssues and PRs related to the SQLite subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@trivikr@nodejs-github-bot@TrevorBurnham@Qard
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

sqlite: reject deserialize() while in a callback - #64796

Merged
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close
Aug 9, 2026
Merged

sqlite: reject deserialize() while in a callback#64796
trivikr merged 1 commit into
nodejs:mainfrom
trivikr:sqlite-reject-reentrant-close

Conversation

@trivikr

@trivikrtrivikr commented Jul 28, 2026

Copy link
Copy Markdown
Member

Refs: #64795

deserialize() could be called from a user-defined function invoked during statement execution, tearing down the database connection while sqlite3_step() was still using it. Reuse the existing callback depth check to throw ERR_INVALID_STATE instead, matching the guard already in place for close().


Assisted-by: codex:gpt-5.6-sol

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/sqlite

@nodejs-github-botnodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. sqlite Issues and PRs related to the SQLite subsystem. labels Jul 28, 2026
@codecov

codecovBot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.33%. Comparing base (bf2f995) to head (b2b7405).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #64796 +/- ##
==========================================
- Coverage 90.33% 90.33% -0.01% 
==========================================
Files 760 760 Lines 248522 248523 +1 Branches 46904 46906 +2 ==========================================
- Hits 224513 224511 -2 - Misses 15444 15451 +7 + Partials 8565 8561 -4 
Files with missing linesCoverage Δ
src/node_sqlite.cc81.23% <100.00%> (+<0.01%)⬆️

... and 29 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@trivikrtrivikr added the request-ci Add this label to start a Jenkins CI on a PR. label Jul 28, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c11abfd to 4af37c6CompareJuly 31, 2026 04:28
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from 4af37c6 to c64b289CompareAugust 3, 2026 19:58
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 3 times, most recently from 46139cd to e6c5c8cCompareAugust 5, 2026 02:22

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guard itself looks right to me: IsInCallback() is the correct predicate, since the crash needs a live sqlite3_step() frame, which is only reachable from a callback, and every step-reentrant entry point (xFunc, xStepBase/xValueBase, the authorizer, sqlite3changeset_apply) already takes CallbackDepthGuard. Placing it before FinalizeStatements() also means a rejected call leaves no partial state. Three non-blocking notes below, plus one on the docs.

doc/api/sqlite.md: the new throwing condition isn't documented. The database.deserialize() section only mentions that existing statements are finalized first; it'd help to state that the method throws ERR_INVALID_STATE when called from a user-defined function, aggregate, authorizer, or changeset filter/conflict callback. The same is missing for database.close() from #64743 — might be worth adding both here.

Comment threadsrc/node_sqlite.cc
Comment threadtest/parallel/test-sqlite-serialize.js Outdated
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from e6c5c8c to 19c3939CompareAugust 6, 2026 18:10
@trivikrtrivikr changed the title sqlite: prevent reentrant statement finalizationsqlite: reject deserialize() while in a callbackAug 6, 2026
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch 2 times, most recently from ae8a08a to 0081aa8CompareAugust 7, 2026 03:34

@TrevorBurnhamTrevorBurnham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround on the commit message and the test rename — both look good, and keeping the test with its deserialize siblings is a fair call.

The C++ guard is unchanged and still correct. I built this branch and confirmed the #64795 repro now throws instead of crashing, that coverage extends past the tested UDF case to aggregate and authorizer callbacks, and that there are no false positives across connections (deserializing an unrelated database from another database's callback still works). The remaining comments are all on the doc commit, plus one on the Fixes: line.

One more docs point that isn't in the diff: database.close() (line 291) has carried this same callback restriction since 5cef767, but its docs still say only "An exception is thrown if the database is not open." Documenting the restriction for deserialize() while leaving close() undocumented is an odd asymmetry — probably worth a one-line addition there while you're in this section.

Comment threaddoc/api/sqlite.md Outdated
Comment threaddoc/api/sqlite.md Outdated
Comment threadsrc/node_sqlite.cc
@TrevorBurnham

Copy link
Copy Markdown
Contributor

Looks ready to 🚢 to me.

One non-blocking note: database.close() is guarded by the same IsInCallback() check as deserialize(), and throws ERR_INVALID_STATE under the same conditions, but its docs don't reflect that. I'd suggest copying the sentence you added to the docs for deserialize() over to the docs for database.close().

@trivikr

Copy link
Copy Markdown
MemberAuthor

The database.close() doc update us posted at #65090

I'll mark it ready for review post rebase after this PR is merged, as they share a reference link.

Qard
Qard approved these changes Aug 9, 2026
@github-actionsgithub-actionsBot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 9, 2026
@nodejs-github-bot

This comment was marked as outdated.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: nodejs#64796
Refs: nodejs#64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
@trivikr
trivikrforce-pushed the sqlite-reject-reentrant-close branch from c32ea43 to b2b7405CompareAugust 9, 2026 23:27
@trivikr
trivikr merged commit b2b7405 into nodejs:mainAug 9, 2026
19 checks passed
@trivikr

Copy link
Copy Markdown
MemberAuthor

Landed in b2b7405

@trivikr
trivikr deleted the sqlite-reject-reentrant-close branch August 9, 2026 23:28
aduh95 pushed a commit that referenced this pull request Aug 13, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
aduh95 pushed a commit that referenced this pull request Aug 25, 2026
deserialize() could be called from a user-defined function invoked
during statement execution, tearing down the database connection while
sqlite3_step() was still using it. Reuse the existing callback depth
check to throw ERR_INVALID_STATE instead, matching the guard already
in place for close().
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: codex:gpt-5.6-sol
PR-URL: #64796
Refs: #64795
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++Issues and PRs that require attention from people who are familiar with C++.needs-ciPRs that need a full CI run.sqliteIssues and PRs related to the SQLite subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@trivikr@nodejs-github-bot@TrevorBurnham@Qard