You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Statement handle crashes on reuse after a server error inside a parameter-array execute #309
When one parameter set of an array execute fails on the server (here a NOT NULL violation in set 3 of 5), SQLExecute returns SQL_ERROR with the server's message, as it should, but the statement handle is left in a state that crashes the process on its next use: SQLFreeStmt(SQL_RESET_PARAMS), SQL_ATTR_PARAMSET_SIZE = 1, two SQLBindParameter calls and an SQLExecDirect end in an access violation inside the driver.
Visible in the same run: SQL_ATTR_PARAMS_PROCESSED_PTR reports 0 (the specification counts the failed set, so 3), and the status array has the two successful sets marked and nothing else (the failed set should be SQL_PARAM_ERROR, the sets after it SQL_PARAM_UNUSED).
Environment
Windows x64, master 35fae3e with #308 applied (the code involved is not touched by #308), Firebird 5.0.3, Microsoft driver manager. The code involved is platform-independent.
Reproduction
CREATE TABLE T_PS4 (ID INTEGER NOT NULL, V BIGINT); INSERT INTO T_PS4 (ID, V) VALUES (?, ?) with SQL_ATTR_PARAMSET_SIZE = 5, column-wise, ID bound SQL_C_SLONG with the indicator of set 3 = SQL_NULL_DATA, status array and processed pointer set. Then reuse the same handle for a single-row insert. Case 5 of a ctypes probe (the status array was pre-filled with 9 to show which elements the driver writes):
5a error in set 3 rc=SQL_ERROR processed=0 status=[OK OK 9 9 9] stored: (1,10) (2,20)
[23000] validation error for column "T_PS4"."ID", value "*** null ***"
5b handle reused, single row OSError: exception: access violation reading 0x000004B12A2014D8
Cause
OdbcStatement::executeStatementParamArray points applicationParamDescriptor->headBindOffsetPtr at a local variable (bindOffsetPtrTmp) for the duration of the loop, and hands the same address to convert->setBindOffsetPtrFrom for row-wise binding. Both are restored on the normal exit and on the inputParam failure exit. statement->executeStatement() throws SQLException on a server error, and there is no third exit path: sqlExecute catches the exception with the descriptor still pointing into the dead stack frame. The next execute dereferences it in rebindParam (*applicationParamDescriptor->headBindOffsetPtr).
The processed count is written only after the loop (*rowCountPt = rowNumberParamArray), so it keeps the 0 written before it; the status element of the failed set is written only on the inputParam failure path.
Expected
The handle survives the error: the offset pointers are restored on every exit path. Processed = 3; status = SQL_PARAM_SUCCESS, SQL_PARAM_SUCCESS, SQL_PARAM_ERROR, SQL_PARAM_UNUSED, SQL_PARAM_UNUSED.
Related: #299 / #308 (stride and execute-time dispatch on the same path). The status pre-fill and the per-set processed count are part of the operation-pointer follow-up; the exception path is not.
Same run on the operation-pointer branch (status array pre-filled, processed count kept per set): no crash this time, the reused handle inserts a garbage row instead of the bound (7, 70):
5a error in set 3 rc=SQL_ERROR processed=3 status=[OK OK UNUSED UNUSED UNUSED] stored: (1,10) (2,20)
5b handle reused, single row rc=SQL_SUCCESS stored: (0,519575431585792) (1,10) (2,20)
Same dangling pointer, read as a garbage offset instead of an unmapped one.
Activity
fdcastel commentedon Sep 7, 2026
Same run on the operation-pointer branch (status array pre-filled, processed count kept per set): no crash this time, the reused handle inserts a garbage row instead of the bound (7, 70):
Same dangling pointer, read as a garbage offset instead of an unmapped one.