From 7f4960657c184f1e280bc4e5b6f38e112c83f1ea Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 15 Jul 2026 16:50:46 +0100 Subject: [PATCH 1/8] standard/file.c: use normal docref function for E_WARNINGs in popen() (#22746) Now that arguments in warnings can be configured to always appear it is no longer necessary to use `php_error_docref2()`. --- ext/standard/file.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/standard/file.c b/ext/standard/file.c index 0bb00355744f..fd6f578e4ef0 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -822,7 +822,7 @@ PHP_FUNCTION(popen) fp = VCWD_POPEN(command, posix_mode); if (!fp) { - php_error_docref2(NULL, command, posix_mode, E_WARNING, "%s", strerror(errno)); + php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); efree(posix_mode); RETURN_FALSE; } @@ -831,7 +831,7 @@ PHP_FUNCTION(popen) stream = php_stream_fopen_from_pipe(fp, mode); if (stream == NULL) { - php_error_docref2(NULL, command, mode, E_WARNING, "%s", strerror(errno)); + php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); RETVAL_FALSE; } else { php_stream_to_zval(stream, return_value); From 19860880d234da95362d0f41d185d81cc722f529 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 15 Jul 2026 16:51:03 +0100 Subject: [PATCH 2/8] exif: change usage of php_error_docref1() to php_error_docref() (#22747) As arguments to functions are now handled globally via an INI setting. --- ext/exif/exif.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/exif/exif.c b/ext/exif/exif.c index 31030e12fbf1..810ce30f38d0 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -4938,7 +4938,7 @@ PHP_FUNCTION(exif_read_data) exif_discard_imageinfo(&ImageInfo); #ifdef EXIF_DEBUG - php_error_docref1(NULL, (Z_TYPE_P(stream) == IS_RESOURCE ? "" : Z_STRVAL_P(stream)), E_NOTICE, "Done"); + php_error_docref(NULL, E_NOTICE, "Done"); #endif } /* }}} */ @@ -5026,7 +5026,7 @@ PHP_FUNCTION(exif_thumbnail) exif_discard_imageinfo(&ImageInfo); #ifdef EXIF_DEBUG - php_error_docref1(NULL, (Z_TYPE_P(stream) == IS_RESOURCE ? "" : Z_STRVAL_P(stream)), E_NOTICE, "Done"); + php_error_docref(NULL, E_NOTICE, "Done"); #endif } /* }}} */ From 345b2a640f7c59e34d209a45ef9a4657a47fa0a2 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Thu, 16 Jul 2026 01:48:55 +0800 Subject: [PATCH 3/8] ext/exif: Fix EXIF warning format specifiers for size_t values (#22750) Fix EXIF warning format specifiers for size_t values Several EXIF warning messages printed `size_t` values using `%d` or `%X`, which does not match the argument type passed through varargs. Use `%zu` / `%zX` for size and offset values to avoid undefined behavior and make the diagnostics portable across platforms. No behavior change is intended apart from the corrected warning formatting. --- ext/exif/exif.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ext/exif/exif.c b/ext/exif/exif.c index 810ce30f38d0..62dca4f5db43 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -2215,7 +2215,7 @@ static void exif_iif_add_value(image_info_type *image_info, int section_index, c switch (format) { case TAG_FMT_STRING: if (length > value_len) { - exif_error_docref("exif_iif_add_value" EXIFERR_CC, image_info, E_WARNING, "length > value_len: %d > %zu", length, value_len); + exif_error_docref("exif_iif_add_value" EXIFERR_CC, image_info, E_WARNING, "length > value_len: %zu > %zu", length, value_len); value = NULL; } if (value) { @@ -2245,7 +2245,7 @@ static void exif_iif_add_value(image_info_type *image_info, int section_index, c ZEND_FALLTHROUGH; case TAG_FMT_UNDEFINED: if (length > value_len) { - exif_error_docref("exif_iif_add_value" EXIFERR_CC, image_info, E_WARNING, "length > value_len: %d > %zu", length, value_len); + exif_error_docref("exif_iif_add_value" EXIFERR_CC, image_info, E_WARNING, "length > value_len: %zu > %zu", length, value_len); value = NULL; } if (value) { @@ -3304,7 +3304,7 @@ static bool exif_process_IFD_TAG_impl(image_info_type *ImageInfo, char *dir_entr * relative to the start of the TIFF header in APP1 section. */ // TODO: Shouldn't we also be taking "displacement" into account here? if (byte_count > ImageInfo->FileSize || offset_val>ImageInfo->FileSize-byte_count || (ImageInfo->FileType!=IMAGE_FILETYPE_TIFF_II && ImageInfo->FileType!=IMAGE_FILETYPE_TIFF_MM && ImageInfo->FileType!=IMAGE_FILETYPE_JPEG)) { - exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Process tag(x%04X=%s): Illegal pointer offset(x%04X + x%04X = x%04X > x%04X)", tag, exif_get_tagname_debug(tag, tag_table), offset_val, byte_count, offset_val+byte_count, ImageInfo->FileSize); + exif_error_docref("exif_read_data#error_ifd" EXIFERR_CC, ImageInfo, E_WARNING, "Process tag(x%04X=%s): Illegal pointer offset(x%04zX + x%04zX = x%04zX > x%04zX)", tag, exif_get_tagname_debug(tag, tag_table), offset_val, byte_count, offset_val+byte_count, ImageInfo->FileSize); return false; } if (byte_count>sizeof(cbuf)) { @@ -3326,7 +3326,7 @@ static bool exif_process_IFD_TAG_impl(image_info_type *ImageInfo, char *dir_entr size_t fgot = php_stream_tell(ImageInfo->infile); if (fgot!=displacement+offset_val) { EFREE_IF(outside); - exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Wrong file pointer: 0x%08X != 0x%08X", fgot, displacement+offset_val); + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Wrong file pointer: 0x%08zX != 0x%08zX", fgot, displacement+offset_val); return false; } fgot = exif_read_from_stream_file_looped(ImageInfo->infile, value_ptr, byte_count); @@ -3866,7 +3866,7 @@ static bool exif_scan_JPEG_header(image_info_type *ImageInfo) got = exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(Data+2), itemlen-2); /* Read the whole section. */ if (got != itemlen-2) { - exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error reading from file: got=x%04X(=%d) != itemlen-2=x%04X(=%d)", got, got, itemlen-2, itemlen-2); + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error reading from file: got=x%04zX(=%zu) != itemlen-2=x%04zX(=%zu)", got, got, itemlen-2, itemlen-2); return false; } @@ -4150,7 +4150,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir if (ImageInfo->FileSize >= ImageInfo->file.list[sn].size && ImageInfo->FileSize - ImageInfo->file.list[sn].size >= dir_offset) { if (ifd_size > dir_size) { if (ImageInfo->FileSize < ifd_size || dir_offset > ImageInfo->FileSize - ifd_size) { - exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than size of IFD(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, ifd_size); + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04zX) less than size of IFD(x%04zX + x%04zX)", ImageInfo->FileSize, dir_offset, ifd_size); return false; } if (exif_file_sections_realloc(ImageInfo, sn, ifd_size)) { @@ -4266,15 +4266,15 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir } return true; } else { - exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than size of IFD(x%04X)", ImageInfo->FileSize, dir_offset+ImageInfo->file.list[sn].size); + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04zX) less than size of IFD(x%04zX)", ImageInfo->FileSize, dir_offset+ImageInfo->file.list[sn].size); return false; } } else { - exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than size of IFD dir(x%04X)", ImageInfo->FileSize, dir_offset+dir_size); + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04zX) less than size of IFD dir(x%04zX)", ImageInfo->FileSize, dir_offset+dir_size); return false; } } else { - exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04X) less than start of IFD dir(x%04X)", ImageInfo->FileSize, dir_offset+2); + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error in TIFF: filesize(x%04zX) less than start of IFD dir(x%04zX)", ImageInfo->FileSize, dir_offset+2); return false; } } @@ -4507,7 +4507,7 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo) ImageInfo->FileType = IMAGE_FILETYPE_UNKNOWN; if (UNEXPECTED(ImageInfo->FileSize < 2)) { - exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File too small (%d)", ImageInfo->FileSize); + exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "File too small (%zu)", ImageInfo->FileSize); return false; } From 0f78dbaab2c183c210d223fd1b153c0bf8525298 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 15 Jul 2026 19:33:48 +0100 Subject: [PATCH 4/8] streams: use built-in argument handling for warnings instead of 2 param functions (#22749) Since all warnings can now display the arguments of the call, the stream layer can just piggyback on-top of it instead of the additional complexity of handling it. This is only a first step as there is still the 1 param variant that needs to be converted. --- ext/standard/tests/file/rename_variation.phpt | 2 +- .../tests/file/rename_variation12.phpt | 9 ++++--- .../tests/file/rename_variation13.phpt | 25 +++++++++++-------- .../tests/file/rename_variation5.phpt | 8 +++--- .../tests/file/rename_variation8.phpt | 4 +-- main/streams/php_stream_errors.h | 15 ----------- main/streams/plain_wrapper.c | 18 ++++++------- main/streams/stream_errors.c | 23 ----------------- 8 files changed, 35 insertions(+), 69 deletions(-) diff --git a/ext/standard/tests/file/rename_variation.phpt b/ext/standard/tests/file/rename_variation.phpt index b86c5c80973d..88ad26080f4a 100644 --- a/ext/standard/tests/file/rename_variation.phpt +++ b/ext/standard/tests/file/rename_variation.phpt @@ -55,7 +55,7 @@ bool(false) bool(true) -- Iteration 2 -- -Warning: rename(%s,%s): Not a directory in %s on line %d +Warning: rename(): Not a directory in %s on line %d bool(false) bool(false) bool(false) diff --git a/ext/standard/tests/file/rename_variation12.phpt b/ext/standard/tests/file/rename_variation12.phpt index 8675b17aab5c..954c12427232 100644 --- a/ext/standard/tests/file/rename_variation12.phpt +++ b/ext/standard/tests/file/rename_variation12.phpt @@ -2,6 +2,9 @@ Test rename() function : variation - various relative, absolute paths --CREDITS-- Dave Kelsey +--INI-- +zend.exception_string_param_max_len=1000000 +error_include_args=On --SKIPIF-- +--INI-- +zend.exception_string_param_max_len=1000000 +error_include_args=On --SKIPIF-- Date: Wed, 15 Jul 2026 14:37:30 -0400 Subject: [PATCH 5/8] Run oversized bz2 resource tests in isolation to avoid OOM (#22744) gh20620 and bzdecompress_input_too_large each allocate ~4GB under RUN_RESOURCE_HEAVY_TESTS; two in parallel exhaust a memory-constrained runner and the OOM killer aborts the whole run. Mark both --CONFLICTS-- all so run-tests executes them in isolation, matching ext/tidy/tests/parsing_file_too_large.phpt. --- ext/bz2/tests/bzdecompress_input_too_large.phpt | 2 ++ ext/bz2/tests/gh20620.phpt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ext/bz2/tests/bzdecompress_input_too_large.phpt b/ext/bz2/tests/bzdecompress_input_too_large.phpt index 88c93d366c54..9774c6dbbfd6 100644 --- a/ext/bz2/tests/bzdecompress_input_too_large.phpt +++ b/ext/bz2/tests/bzdecompress_input_too_large.phpt @@ -10,6 +10,8 @@ if (!getenv('RUN_RESOURCE_HEAVY_TESTS')) die('skip resource-heavy test'); if (getenv('SKIP_SLOW_TESTS')) die('skip slow test'); if (PHP_INT_SIZE != 8) die('skip 64-bit only'); ?> +--CONFLICTS-- +all --FILE-- +--CONFLICTS-- +all --INI-- memory_limit=-1 --FILE-- From 2d86f8cf489ea5410edd19d301c1aefe330599ab Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 7 Jul 2026 20:32:58 +0200 Subject: [PATCH 6/8] Fix leak on double DatePeriod::__construct() call Closes GH-22643 --- NEWS | 2 ++ ext/date/php_date.c | 20 ++++++++++++++++++- .../DatePeriod_double_constructor_call.phpt | 14 +++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 ext/date/tests/DatePeriod_double_constructor_call.phpt diff --git a/NEWS b/NEWS index 12ae8c0fb23d..e9e5b76cea69 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,8 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.2.33 +- Date: + . Fixed leak on double DatePeriod::__construct() call. (ilutov) 02 Jul 2026, PHP 8.2.32 diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 9108a7c246a8..c71cef2cf155 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -4720,6 +4720,23 @@ static bool date_period_initialize(timelib_time **st, timelib_time **et, timelib return retval; } /* }}} */ +static void date_period_reset(php_period_obj *period_obj) +{ + if (period_obj->start) { + timelib_time_dtor(period_obj->start); + } + if (period_obj->current) { + timelib_time_dtor(period_obj->current); + } + if (period_obj->end) { + timelib_time_dtor(period_obj->end); + } + if (period_obj->interval) { + timelib_rel_time_dtor(period_obj->interval); + } + memset(period_obj, 0, XtOffsetOf(php_period_obj, std)); +} + /* {{{ Creates new DatePeriod object. */ PHP_METHOD(DatePeriod, __construct) { @@ -4741,7 +4758,7 @@ PHP_METHOD(DatePeriod, __construct) } dpobj = Z_PHPPERIOD_P(ZEND_THIS); - dpobj->current = NULL; + date_period_reset(dpobj); if (isostr) { if (!date_period_initialize(&(dpobj->start), &(dpobj->end), &(dpobj->interval), &recurrences, isostr, isostr_len)) { @@ -4780,6 +4797,7 @@ PHP_METHOD(DatePeriod, __construct) if (end) { DATE_CHECK_INITIALIZED(Z_PHPDATE_P(end)->time, DateTimeInterface); } + DATE_CHECK_INITIALIZED(Z_PHPINTERVAL_P(interval)->initialized, Z_OBJCE_P(interval)); /* init */ php_interval_obj *intobj = Z_PHPINTERVAL_P(interval); diff --git a/ext/date/tests/DatePeriod_double_constructor_call.phpt b/ext/date/tests/DatePeriod_double_constructor_call.phpt new file mode 100644 index 000000000000..551d2727282d --- /dev/null +++ b/ext/date/tests/DatePeriod_double_constructor_call.phpt @@ -0,0 +1,14 @@ +--TEST-- +Double DatePeriod::__construct() call +--FILE-- +__construct($start, $interval, 1); + +?> +===DONE=== +--EXPECT-- +===DONE=== From 070f8b21861f37c23f6d910f9f797d939ead3949 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 15 Jul 2026 20:53:05 +0200 Subject: [PATCH 7/8] Adjust XtOffsetOf() to offsetof() for master --- ext/date/php_date.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/date/php_date.c b/ext/date/php_date.c index ba660650e256..0e24e4450641 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -5174,7 +5174,7 @@ static void date_period_reset(php_period_obj *period_obj) if (period_obj->interval) { timelib_rel_time_dtor(period_obj->interval); } - memset(period_obj, 0, XtOffsetOf(php_period_obj, std)); + memset(period_obj, 0, offsetof(php_period_obj, std)); } /* {{{ Creates new DatePeriod object. */ From bdcf9ceee383a9d2f4624b90b463279e3a7b1368 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Tue, 14 Jul 2026 19:23:53 -0400 Subject: [PATCH 8/8] Fix pdo_odbc output-buffer leak and stale-binding out-of-bounds read SQLBindParameter recorded deferred pointers (P->outbuf, &P->len, and the bound-parameter struct) into the ODBC statement at PDO_PARAM_EVT_ALLOC, and P->outbuf was never freed. Both outlive the bound-parameter struct, which PDO destroys mid-statement on execute() with an array or a rebind: the buffer leaked and the stale binding made the next SQLExecute read freed memory. Track output buffers on the statement and free them in the destructor, and defer binding to execute time, resetting and rebinding the current parameters when the set changed. Closes GH-22735 --- ext/pdo_odbc/odbc_stmt.c | 79 ++++++++++++++++++---- ext/pdo_odbc/php_pdo_odbc_int.h | 8 ++- ext/pdo_odbc/tests/bound_param_rebind.phpt | 28 ++++++++ ext/pdo_odbc/tests/output_param_leak.phpt | 29 ++++++++ 4 files changed, 129 insertions(+), 15 deletions(-) create mode 100644 ext/pdo_odbc/tests/bound_param_rebind.phpt create mode 100644 ext/pdo_odbc/tests/output_param_leak.phpt diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c index 8940143da09c..8786f2563e52 100644 --- a/ext/pdo_odbc/odbc_stmt.c +++ b/ext/pdo_odbc/odbc_stmt.c @@ -133,6 +133,11 @@ static void free_cols(pdo_stmt_t *stmt, pdo_odbc_stmt *S) } } +static void odbc_free_out_buffer(zval *el) +{ + efree(Z_PTR_P(el)); +} + static int odbc_stmt_dtor(pdo_stmt_t *stmt) { pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; @@ -153,11 +158,41 @@ static int odbc_stmt_dtor(pdo_stmt_t *stmt) if (S->convbuf) { efree(S->convbuf); } + if (S->out_buffers) { + zend_hash_destroy(S->out_buffers); + FREE_HASHTABLE(S->out_buffers); + } efree(S); return 1; } +static bool odbc_bind_param(pdo_stmt_t *stmt, struct pdo_bound_param_data *param) +{ + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + pdo_odbc_param *P = (pdo_odbc_param*)param->driver_data; + RETCODE rc; + + if (!P) { + return true; + } + + rc = SQLBindParameter(S->stmt, (SQLUSMALLINT) param->paramno+1, + P->paramtype, P->ctype, P->sqltype, P->precision, P->scale, + P->paramtype == SQL_PARAM_INPUT ? + (SQLPOINTER)param : + P->outbuf, + P->outbuf ? P->outbuflen : 0, + &P->len + ); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return true; + } + pdo_odbc_stmt_error("SQLBindParameter"); + return false; +} + static int odbc_stmt_execute(pdo_stmt_t *stmt) { RETCODE rc, rc1; @@ -169,6 +204,24 @@ static int odbc_stmt_execute(pdo_stmt_t *stmt) SQLCloseCursor(S->stmt); } + if (S->params_dirty) { + rc = SQLFreeStmt(S->stmt, SQL_RESET_PARAMS); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLFreeStmt: SQL_RESET_PARAMS"); + return 0; + } + if (stmt->bound_params) { + struct pdo_bound_param_data *param; + + ZEND_HASH_FOREACH_PTR(stmt->bound_params, param) { + if (!odbc_bind_param(stmt, param)) { + return 0; + } + } ZEND_HASH_FOREACH_END(); + } + S->params_dirty = false; + } + rc = SQLExecute(S->stmt); while (rc == SQL_NEED_DATA) { @@ -312,6 +365,7 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p if (P) { efree(P); } + S->params_dirty = true; break; case PDO_PARAM_EVT_ALLOC: @@ -386,6 +440,11 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p } P->outbuf = emalloc(P->len + (P->is_unicode ? 2:1)); P->outbuflen = P->len; + if (!S->out_buffers) { + ALLOC_HASHTABLE(S->out_buffers); + zend_hash_init(S->out_buffers, HT_MIN_SIZE, NULL, odbc_free_out_buffer, false); + } + zend_hash_next_index_insert_ptr(S->out_buffers, P->outbuf); } } @@ -394,20 +453,12 @@ static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *p return 0; } - rc = SQLBindParameter(S->stmt, (SQLUSMALLINT) param->paramno+1, - P->paramtype, ctype, sqltype, precision, scale, - P->paramtype == SQL_PARAM_INPUT ? - (SQLPOINTER)param : - P->outbuf, - P->len, - &P->len - ); - - if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { - return 1; - } - pdo_odbc_stmt_error("SQLBindParameter"); - return 0; + P->sqltype = sqltype; + P->ctype = ctype; + P->precision = precision; + P->scale = scale; + S->params_dirty = true; + return 1; } case PDO_PARAM_EVT_EXEC_PRE: diff --git a/ext/pdo_odbc/php_pdo_odbc_int.h b/ext/pdo_odbc/php_pdo_odbc_int.h index ea93c58b2b60..c25fa5e36eae 100644 --- a/ext/pdo_odbc/php_pdo_odbc_int.h +++ b/ext/pdo_odbc/php_pdo_odbc_int.h @@ -145,16 +145,22 @@ typedef struct { pdo_odbc_errinfo einfo; char *convbuf; zend_ulong convbufsize; + HashTable *out_buffers; unsigned going_long:1; unsigned assume_utf8:1; + unsigned params_dirty:1; signed col_count:16; - unsigned _spare:14; + unsigned _spare:13; } pdo_odbc_stmt; typedef struct { SQLLEN len; SQLLEN outbuflen; SQLSMALLINT paramtype; + SQLSMALLINT sqltype; + SQLSMALLINT ctype; + SQLSMALLINT scale; + SQLULEN precision; char *outbuf; unsigned is_unicode:1; unsigned _spare:31; diff --git a/ext/pdo_odbc/tests/bound_param_rebind.phpt b/ext/pdo_odbc/tests/bound_param_rebind.phpt new file mode 100644 index 000000000000..1ae9651f3a7d --- /dev/null +++ b/ext/pdo_odbc/tests/bound_param_rebind.phpt @@ -0,0 +1,28 @@ +--TEST-- +pdo_odbc: a bound parameter does not leave a stale ODBC binding on re-execute +--EXTENSIONS-- +pdo_odbc +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + +$stmt = $pdo->prepare('SELECT ? AS v'); +$v = 'bound'; +$stmt->bindParam(1, $v, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 256); +$stmt->execute(['from_array']); + +var_dump($stmt->fetch(PDO::FETCH_ASSOC)['v']); +?> +--EXPECT-- +string(10) "from_array" diff --git a/ext/pdo_odbc/tests/output_param_leak.phpt b/ext/pdo_odbc/tests/output_param_leak.phpt new file mode 100644 index 000000000000..f6d4aad5a8c6 --- /dev/null +++ b/ext/pdo_odbc/tests/output_param_leak.phpt @@ -0,0 +1,29 @@ +--TEST-- +pdo_odbc: bound output parameter buffer is released (no leak) +--EXTENSIONS-- +pdo_odbc +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + +$stmt = $pdo->prepare('SELECT ? AS v'); +$var = 'seed'; +$stmt->bindParam(1, $var, PDO::PARAM_STR | PDO::PARAM_INPUT_OUTPUT, 256); +$stmt->execute(); +$row = $stmt->fetch(PDO::FETCH_ASSOC); + +var_dump($row['v']); +?> +--EXPECT-- +string(4) "seed"