Fix double callback invoke on unhandled exception - #528
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review. 📝 WalkthroughWalkthroughThis change prevents ChangesCallback exception handling
Priority: ➖ Normal Estimated code review effort: 2 (Simple) | ~10 minutes Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to This change prevents a throwing computeSignature callback from being invoked a second time while preserving synchronous error propagation. The covered behavior is ready to merge. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The callback was invoked inside the `try`, so an exception thrown by the callback itself landed in the `catch` and invoked it a second time with its own error as `err`. Narrow the `try` to cover only `syncVersion`. This keeps the callback synchronous. Deferring it with `process.nextTick` also stops the double invocation, but changes when the callback runs: `computeSignature(xml, cb)` would return before `cb` fires, so an existing caller reading `getSignedXml()` immediately after gets `""` rather than the signed document -- a silent breaking change for a public, semver-bound API. De-Zalgo-ing these callbacks is worth doing, but as a deliberate major. The `return` in the `catch` is compiler-enforced: without it `result` is not definitely assigned and `tsc --strict` rejects the code, so the error path cannot regress into a double invocation. Replace the helper-level tests with one driving the public `computeSignature(xml, callback)` path from the issue. It is synchronous, so it no longer removes and restores the process `uncaughtException` listeners, which leaked mocha's handler when the test failed. Resolves node-saml#527 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@adamjmcgrath , I've made some changes. What do you think? |
Reported and diagnosed by @adamjmcgrath in #527: when a caller's callback throws,
computeSignature(xml, cb)invokes it a second time, passing the callback's own error back aserr.Cause
createOptionalCallbackFunctioninvoked the callback inside thetry, so an exception thrown by the callback landed in thecatchand was handed straight back to it:Present since the helper was introduced in #343, so every release from v4.0.0 onward.
Fix
Narrow the
tryto cover onlysyncVersion. Once the callback is outside it, its exceptions cannot re-enter thecatch:The
returnis enforced by the compiler rather than by discipline — without itresultis not definitely assigned andtsc --strictrejects the file, so the error path cannot regress into a double call.Why not
process.nextTickThe original version of this PR deferred the success callback with
process.nextTick. That also stops the double invocation, but it changes when the callback runs, andcomputeSignature(xml, cb)was effectively synchronous. Measured on the same input, the one line differing:getSignedXml()immediately afterprocess.nextTickAn existing caller reading
getSignedXml()aftercomputeSignature(xml, cb)would get an empty string rather than an exception — a silent breaking change to a semver-bound public API, in a library where the failure surfaces downstream as an unsigned document. Narrowing thetryfixes the reported bug with byte-identical timing instead.De-Zalgoing these callbacks is still worth doing, but as a deliberate major rather than inside a bug fix. Tracked for 7.0 in #546, alongside #545.
Tests
One regression test driving the public
computeSignature(xml, callback)path from the issue. It was watched failing first against the unfixed helper, for the reported reason:It is fully synchronous, so it no longer removes and restores the process
uncaughtExceptionlisteners — the earlier version leaked mocha's handler for the rest of the run whenever it failed.npm run build,npm test(219 passing) andnpm run lintall clean.fixes #527
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests