Repository navigation
Conversation
Fd2dBlackScholesVanillaEngine::calculate() dynamic_pointer_casts arguments_.payoff to BasketPayoff but, unlike its sibling FdNdimBlackScholesVanillaEngine (which already QL_REQUIREs the cast to succeed), had no check at all. A failed cast silently produced a null payoff_, later dereferenced unconditionally in FdmLogBasketInnerValue::innerValue() -- undefined behavior instead of a clean error. Added a regression test that drives the engine directly through its own getArguments()/calculate() interface (the same entry point BasketOption itself uses, but without its constructor-level type safety) with a non-basket payoff. Confirmed it fails (an uncaught Boost shared_ptr null-dereference assertion, not a crash) without the fix and passes (a clean QuantLib::Error) with it. Co-worked with Claude Code
There was a problem hiding this comment.
🟡 Changes recommended
The new quote, process, exercise, and engine allocations use raw new wrapped in shared pointers instead of the repository-required auto with ext::make_shared convention.
1 open issue
What changed in this PR
This PR makes Fd2dBlackScholesVanillaEngine reject non-basket payoffs with a clear QuantLib error instead of risking a null-pointer dereference, matching its N-dimensional sibling.
Changes:
- Adds a guard immediately after the basket-payoff cast.
- Adds a regression test covering normal basket pricing and rejection of a non-basket payoff through the engine’s public interface.
| File | Description |
|---|---|
test-suite/basketoption.cpp |
Tests valid basket pricing and rejection of an incompatible payoff. |
ql/pricingengines/basket/fd2dblackscholesvanillaengine.cpp |
Requires the basket-payoff cast to succeed before proceeding. |
Review effort: Balanced
The new quote/process/exercise/engine allocations wrapped raw new expressions in shared pointers instead of using ext::make_shared, per the repository's own factory convention. Switched all of them. Co-worked with Claude Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Fd2dBlackScholesVanillaEngine::calculate()does:const ext::shared_ptr<BasketPayoff> payoff = ext::dynamic_pointer_cast<BasketPayoff>(arguments_.payoff);with no check that the cast succeeded. Its sibling
FdNdimBlackScholesVanillaEngineperforms the identical cast and immediately follows it withQL_REQUIRE(payoff, "basket payoff expected");—Fd2dBlackScholesVanillaEngineis missing that guard. A failed cast leavespayoffnull, which is stored and later dereferenced unconditionally inFdmLogBasketInnerValue::innerValue()via(*payoff_)(x)— undefined behavior instead of a clean, actionable error.BasketOption's own constructor only accepts anext::shared_ptr<BasketPayoff>, so this isn't reachable through normalBasketOptionconstruction — but the engine's owngetArguments()/calculate()interface is public API independent of any specific instrument wrapper (as the N-dim engine's existing guard already anticipates), and nothing stops a non-basket payoff from being set directly through it.Fix
Added the same
QL_REQUIRE(payoff, "basket payoff expected");the N-dim sibling already has, right after the cast.Test
Added
testFd2dBlackScholesVanillaEngineRejectsNonBasketPayofftotest-suite/basketoption.cpp: drives the engine directly throughgetArguments()/calculate()with a non-basket payoff after confirming normalBasketOptionusage still works. Without the fix this hits an uncaught Boostshared_ptrnull-dereference assertion (px != 0) rather than the library's own clean error; with the fix it throws aQuantLib::Erroras expected. FullBasketOptionTestssuite (33 cases) passes with no regressions.🤖 Co-worked with Claude Code