Fix: catch C++ exception in duckdb_scalar_function_bind_get_argument to prevent process crash#67
Open
krleonid wants to merge 5 commits into
Open
Fix: catch C++ exception in duckdb_scalar_function_bind_get_argument to prevent process crash#67krleonid wants to merge 5 commits into
krleonid wants to merge 5 commits into
Conversation
BoundSubqueryExpression::Copy() throws SerializationException when a scalar UDF with a ScalarBinder receives a correlated subquery argument. Before the fix in duckdb_scalar_function_bind_get_argument, this exception escaped the C API boundary and called std::terminate() because CGo frames have no C++ unwind tables. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BoundSubqueryExpression::Copy() throws SerializationException when a scalar UDF with a ScalarBinder receives a correlated subquery argument. The exception escaped the C API boundary through CGo/C frames that have no C++ unwind tables, reaching std::terminate() -> abort(). Catch at the C boundary, surface via duckdb_scalar_function_bind_set_error, and return nullptr so callers see a normal query error instead of a crash. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This was referenced Jul 2, 2026
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
krleonid
added a commit
to krleonid/duckdb-go
that referenced
this pull request
Jul 2, 2026
When a scalar UDF has a ScalarBinder and receives a correlated subquery argument, DuckDB internally calls BoundSubqueryExpression::Copy() which throws SerializationException. Without a try/catch at the C boundary, the exception unwinds through Go frames (which have no C++ unwind tables), reaches std::terminate(), and aborts the process — recover() cannot help. The DuckDB-side fix (PR krleonid/duckdb#67, targeting v1.5-variegata) wraps the Copy() call in duckdb_scalar_function_bind_get_argument in a try/catch: on exception it sets the bind error via duckdb_scalar_function_bind_set_error and returns nullptr. This commit adds the corresponding duckdb-go guard: getScalarUDFArg now checks for a nil Expression and returns an error instead of proceeding to call ExpressionIsFoldable on a null pointer. The bind error set by DuckDB propagates naturally, and QueryContext returns a normal Go error instead of crashing the process. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
krleonid
added a commit
to krleonid/duckdb-go
that referenced
this pull request
Jul 2, 2026
When a scalar UDF has a ScalarBinder and receives a correlated subquery argument, DuckDB internally calls BoundSubqueryExpression::Copy() which throws SerializationException. Without a try/catch at the C boundary, the exception unwinds through Go frames (which have no C++ unwind tables), reaches std::terminate(), and aborts the process — recover() cannot help. The DuckDB-side fix (PR krleonid/duckdb#67, targeting v1.5-variegata) wraps the Copy() call in duckdb_scalar_function_bind_get_argument in a try/catch: on exception it sets the bind error via duckdb_scalar_function_bind_set_error and returns nullptr. This commit adds the corresponding duckdb-go guard: getScalarUDFArg now checks for a nil Expression and returns an error instead of proceeding to call ExpressionIsFoldable on a null pointer. The bind error set by DuckDB propagates naturally, and QueryContext returns a normal Go error instead of crashing the process. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ExpressionWrapper was allocated before Copy() threw, but the catch block returned nullptr without freeing it. LeakSanitizer caught this on the upstream CI run. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9b241d5 to
98b4262
Compare
Replaces manual new/delete with make_uniq so the wrapper is automatically freed if Copy() throws, without needing an explicit delete in the catch block. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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
BoundSubqueryExpression::Copy()throwsSerializationExceptionwhen a scalar UDF with aScalarBinderreceives a correlated subquery as an argumentstd::terminate()→abort()— uncatchable from GoCopy()call in atry/catchinsideduckdb_scalar_function_bind_get_argument(src/main/capi/scalar_function-c.cpp), sets the error viaduckdb_scalar_function_bind_set_error, and returnsnullptr— surfacing it as a normal query errortest/api/capi/capi_scalar_functions.cppverifies the query fails gracefully instead of aborting the processTest plan
test/api/capi/capi_scalar_functions.cpp— new case inTest Scalar Function with Bind Info: passes a correlated subquery toget_connection_id(which uses aScalarBinder) and assertsREQUIRE_FAILinstead of process abort🤖 Generated with Claude Code