From abf1ce5f0e5af31ca8d84abbba0276d864d1f0c9 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 29 Apr 2026 10:30:46 +0100 Subject: [PATCH 1/4] ext/phar: add const qualifiers (#21907) --- ext/phar/dirstream.c | 2 +- ext/phar/phar.c | 16 ++++++++-------- ext/phar/phar_internal.h | 14 +++++++------- ext/phar/phar_object.c | 5 +++-- ext/phar/stream.c | 11 +++++------ ext/phar/tar.c | 6 +++--- ext/phar/util.c | 2 +- ext/phar/zip.c | 2 +- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/ext/phar/dirstream.c b/ext/phar/dirstream.c index 2b74a566411f..bb46ece5b918 100644 --- a/ext/phar/dirstream.c +++ b/ext/phar/dirstream.c @@ -300,7 +300,7 @@ php_stream *phar_wrapper_open_dir(php_stream_wrapper *wrapper, const char *path, const char *internal_file = ZSTR_VAL(resource->path) + 1; /* strip leading "/" */ size_t internal_file_len = ZSTR_LEN(resource->path) - 1; - phar_entry_info *entry = zend_hash_str_find_ptr(&phar->manifest, internal_file, internal_file_len); + const phar_entry_info *entry = zend_hash_str_find_ptr(&phar->manifest, internal_file, internal_file_len); php_stream *ret; if (NULL != entry && !entry->is_dir) { diff --git a/ext/phar/phar.c b/ext/phar/phar.c index 6d9dd5324945..cd363fbd88ee 100644 --- a/ext/phar/phar.c +++ b/ext/phar/phar.c @@ -484,7 +484,7 @@ ZEND_ATTRIBUTE_NONNULL void phar_entry_remove(phar_entry_data *idata, char **err /** * Open an already loaded phar */ -static zend_result phar_open_parsed_phar(char *fname, size_t fname_len, char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */ +static zend_result phar_open_parsed_phar(char *fname, size_t fname_len, const char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */ { #ifdef PHP_WIN32 char *save_fname; @@ -720,7 +720,7 @@ void phar_parse_metadata_lazy(const char *buffer, phar_metadata_tracker *tracker * This is used by phar_open_from_filename to process the manifest, but can be called * directly. */ -static zend_result phar_parse_pharfile(php_stream *fp, char *fname, size_t fname_len, char *alias, size_t alias_len, zend_long halt_offset, phar_archive_data** pphar, uint32_t compression, char **error) /* {{{ */ +static zend_result phar_parse_pharfile(php_stream *fp, const char *fname, size_t fname_len, const char *alias, size_t alias_len, zend_long halt_offset, phar_archive_data** pphar, uint32_t compression, char **error) /* {{{ */ { char b32[4], *buffer, *endbuffer, *savebuf; phar_archive_data *mydata = NULL; @@ -1302,7 +1302,7 @@ static zend_result phar_parse_pharfile(php_stream *fp, char *fname, size_t fname /** * Create or open a phar for writing */ -ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_open_or_create_filename(zend_string *fname, char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */ +ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_open_or_create_filename(zend_string *fname, const char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */ { const char *ext_str, *z; char *my_error; @@ -1366,9 +1366,9 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_open_or_create_filename(ze } /* }}} */ -static zend_result phar_open_from_fp(php_stream* fp, char *fname, size_t fname_len, char *alias, size_t alias_len, uint32_t options, phar_archive_data** pphar, char **error); +static zend_result phar_open_from_fp(php_stream* fp, const char *fname, size_t fname_len, const char *alias, size_t alias_len, uint32_t options, phar_archive_data** pphar, char **error); -ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_create_or_parse_filename(zend_string *fname, char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */ +ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_create_or_parse_filename(zend_string *fname, const char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */ { php_stream *fp; zend_string *actual = NULL; @@ -1507,7 +1507,7 @@ ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_create_or_parse_filename(z * that the manifest is proper, then pass it to phar_parse_pharfile(). SUCCESS * or FAILURE is returned and pphar is set to a pointer to the phar's manifest */ -zend_result phar_open_from_filename(char *fname, size_t fname_len, char *alias, size_t alias_len, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */ +zend_result phar_open_from_filename(char *fname, size_t fname_len, const char *alias, size_t alias_len, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */ { php_stream *fp; zend_string *actual; @@ -1564,7 +1564,7 @@ zend_result phar_open_from_filename(char *fname, size_t fname_len, char *alias, * that the manifest is proper, then pass it to phar_parse_pharfile(). SUCCESS * or FAILURE is returned and pphar is set to a pointer to the phar's manifest */ -static zend_result phar_open_from_fp(php_stream* fp, char *fname, size_t fname_len, char *alias, size_t alias_len, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */ +static zend_result phar_open_from_fp(php_stream* fp, const char *fname, size_t fname_len, const char *alias, size_t alias_len, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */ { static const char token[] = "__HALT_COMPILER();"; static const char zip_magic[] = "PK\x03\x04"; @@ -2252,7 +2252,7 @@ zend_string* phar_split_fname(const char *filename, size_t filename_len, zend_st * Invoked when a user calls Phar::mapPhar() from within an executing .phar * to set up its manifest directly */ -ZEND_ATTRIBUTE_NONNULL_ARGS(3) zend_result phar_open_executed_filename(char *alias, size_t alias_len, char **error) /* {{{ */ +ZEND_ATTRIBUTE_NONNULL_ARGS(3) zend_result phar_open_executed_filename(const char *alias, size_t alias_len, char **error) /* {{{ */ { *error = NULL; diff --git a/ext/phar/phar_internal.h b/ext/phar/phar_internal.h index b45fb2d2a13a..7a923fd15e18 100644 --- a/ext/phar/phar_internal.h +++ b/ext/phar/phar_internal.h @@ -406,10 +406,10 @@ void phar_object_init(void); void phar_destroy_phar_data(phar_archive_data *phar); ZEND_ATTRIBUTE_NONNULL zend_result phar_postprocess_file(phar_entry_data *idata, uint32_t crc32, char **error, int process_zip); -zend_result phar_open_from_filename(char *fname, size_t fname_len, char *alias, size_t alias_len, uint32_t options, phar_archive_data** pphar, char **error); -ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_open_or_create_filename(zend_string *fname, char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error); -ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_create_or_parse_filename(zend_string *fname, char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error); -ZEND_ATTRIBUTE_NONNULL_ARGS(3) zend_result phar_open_executed_filename(char *alias, size_t alias_len, char **error); +zend_result phar_open_from_filename(char *fname, size_t fname_len, const char *alias, size_t alias_len, uint32_t options, phar_archive_data** pphar, char **error); +ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_open_or_create_filename(zend_string *fname, const char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error); +ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_create_or_parse_filename(zend_string *fname, const char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error); +ZEND_ATTRIBUTE_NONNULL_ARGS(3) zend_result phar_open_executed_filename(const char *alias, size_t alias_len, char **error); zend_result phar_free_alias(const phar_archive_data *phar); phar_archive_data* phar_get_archive(const char *fname, size_t fname_len, const char *alias, size_t alias_len, char **error); zend_result phar_verify_signature(php_stream *fp, size_t end_of_phar, uint32_t sig_type, const char *sig, size_t sig_len, const char *fname, char **signature, size_t *signature_len, char **error); @@ -423,7 +423,7 @@ const char *phar_compress_filter(const phar_entry_info *entry, bool return_unkno /* void phar_remove_virtual_dirs(phar_archive_data *phar, char *filename, size_t filename_len); */ void phar_add_virtual_dirs(phar_archive_data *phar, const char *filename, size_t filename_len); zend_result phar_mount_entry(phar_archive_data *phar, const char *filename, size_t filename_len, char *path, size_t path_len); -zend_string *phar_find_in_include_path(zend_string *file, phar_archive_data **pphar); +zend_string *phar_find_in_include_path(const zend_string *file, phar_archive_data **pphar); zend_string* phar_fix_filepath(const char *path, size_t path_length, bool use_cwd); ZEND_ATTRIBUTE_NONNULL phar_entry_info * phar_open_jit(const phar_archive_data *phar, phar_entry_info *entry, char **error); void phar_parse_metadata_lazy(const char *buffer, phar_metadata_tracker *tracker, uint32_t zip_metadata_len, bool persistent); @@ -446,12 +446,12 @@ zend_result phar_copy_on_write(phar_archive_data **pphar); /* tar functions in tar.c */ bool phar_is_tar(const char *buf, const char *fname); zend_result phar_parse_tarfile(php_stream* fp, const char *fname, size_t fname_len, const char *alias, size_t alias_len, phar_archive_data** pphar, uint32_t compression, char **error); -ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_open_or_create_tar(zend_string *fname, char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error); +ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_open_or_create_tar(zend_string *fname, const char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error); ZEND_ATTRIBUTE_NONNULL_ARGS(1, 4) int phar_tar_flush(phar_archive_data *phar, zend_string *user_stub, bool is_default_stub, char **error); /* zip functions in zip.c */ zend_result phar_parse_zipfile(php_stream *fp, const char *fname, size_t fname_len, const char *alias, size_t alias_len, phar_archive_data** pphar, char **error); -ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_open_or_create_zip(zend_string *fname, char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error); +ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_open_or_create_zip(zend_string *fname, const char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error); ZEND_ATTRIBUTE_NONNULL_ARGS(1, 4) int phar_zip_flush(phar_archive_data *archive, zend_string *user_stub, bool is_default_stub, char **error); #ifdef PHAR_MAIN diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 1766d9f778eb..00a5ebf3058f 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -349,9 +349,10 @@ static void phar_do_404(phar_archive_data *phar, char *fname, size_t fname_len, /* post-process REQUEST_URI and retrieve the actual request URI. This is for cases like http://localhost/blah.phar/path/to/file.php/extra/stuff which calls "blah.phar" file "path/to/file.php" with PATH_INFO "/extra/stuff" */ -static void phar_postprocess_ru_web(const char *fname, size_t fname_len, char *entry, size_t *entry_len, char **ru, size_t *ru_len) /* {{{ */ +static void phar_postprocess_ru_web(const char *fname, size_t fname_len, const char *entry, size_t *entry_len, char **ru, size_t *ru_len) /* {{{ */ { - char *e = entry + 1, *u1 = NULL, *u = NULL, *saveu = NULL; + const char *e = entry + 1; + char *u1 = NULL, *u = NULL, *saveu = NULL; size_t e_len = *entry_len - 1, u_len = 0; phar_archive_data *pphar; diff --git a/ext/phar/stream.c b/ext/phar/stream.c index 9d24fa64d75c..f38f179238ff 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -364,7 +364,7 @@ static ssize_t phar_stream_read(php_stream *stream, char *buf, size_t count) /* { phar_entry_data *data = (phar_entry_data *)stream->abstract; ssize_t got; - phar_entry_info *entry; + const phar_entry_info *entry; if (data->internal_file->symlink) { entry = phar_get_link_source(data->internal_file); @@ -394,7 +394,7 @@ static ssize_t phar_stream_read(php_stream *stream, char *buf, size_t count) /* static int phar_stream_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) /* {{{ */ { phar_entry_data *data = (phar_entry_data *)stream->abstract; - phar_entry_info *entry; + const phar_entry_info *entry; int res; zend_ulong temp; @@ -462,7 +462,7 @@ static int phar_stream_flush(php_stream *stream) /* {{{ */ { char *error; int ret; - phar_entry_data *data = (phar_entry_data *) stream->abstract; + const phar_entry_data *data = stream->abstract; if (data->internal_file->is_modified) { data->internal_file->timestamp = time(0); @@ -536,7 +536,7 @@ void phar_dostat(phar_archive_data *phar, phar_entry_info *data, php_stream_stat */ static int phar_stream_stat(php_stream *stream, php_stream_statbuf *ssb) /* {{{ */ { - phar_entry_data *data = (phar_entry_data *)stream->abstract; + const phar_entry_data *data = stream->abstract; /* If ssb is NULL then someone is misbehaving */ if (!ssb) { @@ -659,7 +659,6 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int char *internal_file, *error; size_t internal_file_len; phar_entry_data *idata; - phar_archive_data *pphar; php_url *resource = phar_parse_url(wrapper, url, "rb", options); if (!resource) { @@ -682,7 +681,7 @@ static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int phar_request_initialize(); - pphar = zend_hash_find_ptr(&(PHAR_G(phar_fname_map)), resource->host); + const phar_archive_data *pphar = zend_hash_find_ptr(&(PHAR_G(phar_fname_map)), resource->host); if (PHAR_G(readonly) && (!pphar || !pphar->is_data)) { php_url_free(resource); php_stream_wrapper_log_error(wrapper, options, "phar error: write operations disabled by the php.ini setting phar.readonly"); diff --git a/ext/phar/tar.c b/ext/phar/tar.c index 761246000dd2..edb0707a6ff7 100644 --- a/ext/phar/tar.c +++ b/ext/phar/tar.c @@ -126,7 +126,7 @@ bool phar_is_tar(const char *buf, const char *fname) /* {{{ */ } /* }}} */ -ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_open_or_create_tar(zend_string *fname, char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */ +ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_open_or_create_tar(zend_string *fname, const char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */ { phar_archive_data *phar; zend_result ret = phar_create_or_parse_filename(fname, alias, alias_len, is_data, options, &phar, error); @@ -802,7 +802,7 @@ static int phar_tar_writeheaders_int(phar_entry_info *entry, void *argument) /* /* write header */ entry->header_offset = php_stream_tell(fp->new); - if (sizeof(header) != php_stream_write(fp->new, (char *) &header, sizeof(header))) { + if (sizeof(header) != php_stream_write(fp->new, (const char *) &header, sizeof(header))) { if (fp->error) { spprintf(fp->error, 4096, "tar-based phar \"%s\" cannot be created, header for file \"%s\" could not be written", ZSTR_VAL(entry->phar->fname), ZSTR_VAL(entry->filename)); } @@ -906,7 +906,7 @@ ZEND_ATTRIBUTE_NONNULL static int phar_tar_setmetadata(const phar_metadata_track ZEND_ATTRIBUTE_NONNULL static int phar_tar_setupmetadata(zval *zv, void *argument) /* {{{ */ { - struct _phar_pass_tar_info *i = (struct _phar_pass_tar_info *)argument; + const struct _phar_pass_tar_info *i = (struct _phar_pass_tar_info *)argument; char **error = i->error; phar_entry_info *entry = (phar_entry_info *)Z_PTR_P(zv), newentry = {0}; diff --git a/ext/phar/util.c b/ext/phar/util.c index 331bbf6511ba..e501ccfaceba 100644 --- a/ext/phar/util.c +++ b/ext/phar/util.c @@ -264,7 +264,7 @@ zend_result phar_mount_entry(phar_archive_data *phar, const char *filename, size } /* }}} */ -zend_string *phar_find_in_include_path(zend_string *filename, phar_archive_data **pphar) /* {{{ */ +zend_string *phar_find_in_include_path(const zend_string *filename, phar_archive_data **pphar) /* {{{ */ { zend_string *ret; char *path; diff --git a/ext/phar/zip.c b/ext/phar/zip.c index 8f61977a3835..900b41f9c794 100644 --- a/ext/phar/zip.c +++ b/ext/phar/zip.c @@ -794,7 +794,7 @@ zend_result phar_parse_zipfile(php_stream *fp, const char *fname, size_t fname_l /** * Create or open a zip-based phar for writing */ -ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_open_or_create_zip(zend_string *fname, char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */ +ZEND_ATTRIBUTE_NONNULL_ARGS(1, 6, 7) zend_result phar_open_or_create_zip(zend_string *fname, const char *alias, size_t alias_len, bool is_data, uint32_t options, phar_archive_data** pphar, char **error) /* {{{ */ { phar_archive_data *phar; zend_result ret = phar_create_or_parse_filename(fname, alias, alias_len, is_data, options, &phar, error); From f1d6391f77e61a05a8d78ee07d14aa3fb2618e88 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 29 Apr 2026 10:31:36 +0100 Subject: [PATCH 2/4] ext/phar: mark phar_dostat() as static and mark parameters as const (#21908) And remove unused declaration from dirstream.c file --- ext/phar/dirstream.c | 2 -- ext/phar/stream.c | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/ext/phar/dirstream.c b/ext/phar/dirstream.c index bb46ece5b918..6c356c04e5e1 100644 --- a/ext/phar/dirstream.c +++ b/ext/phar/dirstream.c @@ -19,8 +19,6 @@ #include "phar_internal.h" #include "dirstream.h" -void phar_dostat(phar_archive_data *phar, phar_entry_info *data, php_stream_statbuf *ssb, bool is_dir); - static const php_stream_ops phar_dir_ops = { phar_dir_write, /* write */ phar_dir_read, /* read */ diff --git a/ext/phar/stream.c b/ext/phar/stream.c index f38f179238ff..f9d19a04122a 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -482,7 +482,7 @@ static int phar_stream_flush(php_stream *stream) /* {{{ */ /** * stat an opened phar file handle stream, used by phar_stat() */ -void phar_dostat(phar_archive_data *phar, phar_entry_info *data, php_stream_statbuf *ssb, bool is_temp_dir) +static void phar_dostat(const phar_archive_data *phar, const phar_entry_info *data, php_stream_statbuf *ssb, bool is_temp_dir) { memset(ssb, 0, sizeof(php_stream_statbuf)); From 392a84d7e1d39a55081bf00ee49f85cf45412d06 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 27 Apr 2026 12:39:01 +0100 Subject: [PATCH 3/4] ext/pgsql: clean up pg_fetch_object() constructor handling. Move object_init_ex() before php_pgsql_fetch_hash() so abstract, interface and enum errors surface earlier, look up the constructor via the get_constructor handler (under EG(fake_scope)) so classes with overridden handlers work, and mark the object as ctor-failed when the constructor throws. The ValueError for ctor args on a class without a constructor now reports argument 4 ($constructor_args) instead of argument 3 ($class). close GH-21884 --- NEWS | 4 ++ UPGRADING | 7 ++ ext/pgsql/pgsql.c | 41 +++++++---- .../tests/pg_fetch_object_ctor_paths.phpt | 69 +++++++++++++++++++ 4 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 ext/pgsql/tests/pg_fetch_object_ctor_paths.phpt diff --git a/NEWS b/NEWS index 3da5464750cc..3144cdf88c59 100644 --- a/NEWS +++ b/NEWS @@ -104,6 +104,10 @@ PHP NEWS - PGSQL: . Enabled 64 bits support for pg_lo_truncate()/pg_lo_tell() if the server supports it. (KentarouTakeda) + . pg_fetch_object() now surfaces non-instantiable class errors + before fetching, resolves the constructor via the get_constructor + handler, and reports the empty-constructor ValueError on the + $constructor_args argument. (David Carlier) - Phar: . Support reference values in Phar::mungServer(). (ndossche) diff --git a/UPGRADING b/UPGRADING index 56049f30cf93..6777820642f6 100644 --- a/UPGRADING +++ b/UPGRADING @@ -47,6 +47,13 @@ PHP 8.6 UPGRADE NOTES . Phar::mungServer() now raises a ValueError when an invalid argument value is passed instead of being silently ignored. +- PGSQL: + . pg_fetch_object() now reports the ValueError for a non-empty + $constructor_args on a class without a constructor on the + $constructor_args argument instead of $class. Errors raised when + the requested class is not instantiable (abstract, interface, enum) + now surface before the row is fetched. + - Posix: . posix_access() now raises a ValueError when an invalid $flags argument value is passed. diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 1674625429ad..8cb022c79cd1 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -2129,26 +2129,17 @@ PHP_FUNCTION(pg_fetch_object) ce = zend_standard_class_def; } - if (!ce->constructor && ctor_params && zend_hash_num_elements(ctor_params) > 0) { - zend_argument_value_error(3, - "must be empty when the specified class (%s) does not have a constructor", - ZSTR_VAL(ce->name) - ); + if (UNEXPECTED(object_init_ex(return_value, ce) == FAILURE)) { RETURN_THROWS(); } zval dataset; if (UNEXPECTED(!php_pgsql_fetch_hash(&dataset, result, row, row_is_null, PGSQL_ASSOC))) { /* Either an exception is thrown, or we return false */ + zval_ptr_dtor(return_value); RETURN_FALSE; } - // TODO: Check CE is an instantiable class earlier? - zend_result obj_initialized = object_init_ex(return_value, ce); - if (UNEXPECTED(obj_initialized == FAILURE)) { - zval_ptr_dtor(&dataset); - RETURN_THROWS(); - } if (!ce->default_properties_count && !ce->__set) { Z_OBJ_P(return_value)->properties = Z_ARR(dataset); } else { @@ -2156,10 +2147,32 @@ PHP_FUNCTION(pg_fetch_object) zval_ptr_dtor(&dataset); } - // TODO: Need to grab constructor via object handler as this allows instantiating internal objects with overridden get_constructor - if (ce->constructor) { - zend_call_known_function(ce->constructor, Z_OBJ_P(return_value), Z_OBJCE_P(return_value), + zend_object *obj = Z_OBJ_P(return_value); + const zend_class_entry *old = EG(fake_scope); + EG(fake_scope) = ce; + zend_function *constructor = obj->handlers->get_constructor(obj); + EG(fake_scope) = old; + + if (UNEXPECTED(EG(exception))) { + /* visibility error or override refused - VM dtors return_value */ + return; + } + + if (UNEXPECTED(!constructor && ctor_params && zend_hash_num_elements(ctor_params) > 0)) { + zend_argument_value_error(4, + "must be empty when the specified class (%s) does not have a constructor", + ZSTR_VAL(ce->name) + ); + RETURN_THROWS(); + } + + if (constructor) { + zend_call_known_function(constructor, obj, ce, /* retval */ NULL, /* argc */ 0, /* params */ NULL, ctor_params); + if (EG(exception)) { + zend_object_store_ctor_failed(obj); + RETURN_THROWS(); + } } } /* }}} */ diff --git a/ext/pgsql/tests/pg_fetch_object_ctor_paths.phpt b/ext/pgsql/tests/pg_fetch_object_ctor_paths.phpt new file mode 100644 index 000000000000..bd3df73acba4 --- /dev/null +++ b/ext/pgsql/tests/pg_fetch_object_ctor_paths.phpt @@ -0,0 +1,69 @@ +--TEST-- +pg_fetch_object() constructor handling: ctor_params validation, throwing constructor, property visibility +--EXTENSIONS-- +pgsql +--SKIPIF-- + +--FILE-- +num}, str={$this->str}\n"; + } + public function __destruct() { + echo "SeesProps::__destruct called\n"; + } +} + +$table_name = "pg_fetch_object_ctor_paths"; +$db = pg_connect($conn_str); +pg_query($db, "CREATE TABLE {$table_name} (num int, str text)"); +pg_query($db, "INSERT INTO {$table_name} VALUES(1, 'hello')"); + +$sql = "SELECT * FROM {$table_name} WHERE num = 1"; + +// 1) ctor_params on a class with no constructor must throw ValueError +try { + pg_fetch_object(pg_query($db, $sql), null, 'NoCtor', [1, 2]); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +// 2) Constructor that throws: __destruct must NOT run on the partially constructed object +try { + pg_fetch_object(pg_query($db, $sql), null, 'ThrowingCtor'); +} catch (RuntimeException $e) { + echo "caught: ", $e->getMessage(), "\n"; +} + +// 3) Constructor sees row properties already merged onto $this +$obj = pg_fetch_object(pg_query($db, $sql), null, 'SeesProps'); +unset($obj); + +echo "Ok\n"; +?> +--CLEAN-- + +--EXPECT-- +pg_fetch_object(): Argument #4 ($constructor_args) must be empty when the specified class (NoCtor) does not have a constructor +caught: boom +ctor sees: num=1, str=hello +SeesProps::__destruct called +Ok From 0d9ff00394d9447992bb66ccb1cef3edf70576bd Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Wed, 29 Apr 2026 10:00:01 -0400 Subject: [PATCH 4/4] ext/odbc: Get the exact length for quoted connection string components (#21892) * ext/odbc: get exact length for quoted connection strings strlen has to scan the string until the end of the null terminator, we might as well do the same but scan for end quotes that need escaping. * ext/odbc: rename function now that it gets the exact length rather than an estimate --- ext/odbc/odbc_utils.c | 2 +- ext/odbc/php_odbc.c | 12 ++++++------ ext/pdo_odbc/odbc_driver.c | 12 ++++++------ main/php_odbc_utils.c | 16 ++++++++++++---- main/php_odbc_utils.h | 2 +- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/ext/odbc/odbc_utils.c b/ext/odbc/odbc_utils.c index 742bf54bb292..8468fa9a06c1 100644 --- a/ext/odbc/odbc_utils.c +++ b/ext/odbc/odbc_utils.c @@ -57,7 +57,7 @@ PHP_FUNCTION(odbc_connection_string_quote) Z_PARAM_STR(str) ZEND_PARSE_PARAMETERS_END(); - size_t new_size = php_odbc_connstr_estimate_quote_length(ZSTR_VAL(str)); + size_t new_size = php_odbc_connstr_get_quoted_length(ZSTR_VAL(str)); zend_string *new_string = zend_string_alloc(new_size, 0); php_odbc_connstr_quote(ZSTR_VAL(new_string), ZSTR_VAL(str), new_size); /* reset length */ diff --git a/ext/odbc/php_odbc.c b/ext/odbc/php_odbc.c index 2d6331b0f544..c9f1b3f2e147 100644 --- a/ext/odbc/php_odbc.c +++ b/ext/odbc/php_odbc.c @@ -1951,9 +1951,9 @@ bool odbc_sqlconnect(zval *zv, char *db, char *uid, char *pwd, int cur_opt, bool if (use_uid_arg) { should_quote_uid = !php_odbc_connstr_is_quoted(uid) && php_odbc_connstr_should_quote(uid); if (should_quote_uid) { - size_t estimated_length = php_odbc_connstr_estimate_quote_length(uid); - uid_quoted = emalloc(estimated_length); - php_odbc_connstr_quote(uid_quoted, uid, estimated_length); + size_t quoted_length = php_odbc_connstr_get_quoted_length(uid); + uid_quoted = emalloc(quoted_length); + php_odbc_connstr_quote(uid_quoted, uid, quoted_length); } else { uid_quoted = uid; } @@ -1966,9 +1966,9 @@ bool odbc_sqlconnect(zval *zv, char *db, char *uid, char *pwd, int cur_opt, bool if (use_pwd_arg) { should_quote_pwd = !php_odbc_connstr_is_quoted(pwd) && php_odbc_connstr_should_quote(pwd); if (should_quote_pwd) { - size_t estimated_length = php_odbc_connstr_estimate_quote_length(pwd); - pwd_quoted = emalloc(estimated_length); - php_odbc_connstr_quote(pwd_quoted, pwd, estimated_length); + size_t quoted_length = php_odbc_connstr_get_quoted_length(pwd); + pwd_quoted = emalloc(quoted_length); + php_odbc_connstr_quote(pwd_quoted, pwd, quoted_length); } else { pwd_quoted = pwd; } diff --git a/ext/pdo_odbc/odbc_driver.c b/ext/pdo_odbc/odbc_driver.c index 4c627419d18e..3c8afe2d4215 100644 --- a/ext/pdo_odbc/odbc_driver.c +++ b/ext/pdo_odbc/odbc_driver.c @@ -548,9 +548,9 @@ static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ if (use_uid_arg) { should_quote_uid = !php_odbc_connstr_is_quoted(dbh->username) && php_odbc_connstr_should_quote(dbh->username); if (should_quote_uid) { - size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->username); - uid = emalloc(estimated_length); - php_odbc_connstr_quote(uid, dbh->username, estimated_length); + size_t quoted_length = php_odbc_connstr_get_quoted_length(dbh->username); + uid = emalloc(quoted_length); + php_odbc_connstr_quote(uid, dbh->username, quoted_length); } else { uid = dbh->username; } @@ -565,9 +565,9 @@ static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ if (use_pwd_arg) { should_quote_pwd = !php_odbc_connstr_is_quoted(dbh->password) && php_odbc_connstr_should_quote(dbh->password); if (should_quote_pwd) { - size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->password); - pwd = emalloc(estimated_length); - php_odbc_connstr_quote(pwd, dbh->password, estimated_length); + size_t quoted_length = php_odbc_connstr_get_quoted_length(dbh->password); + pwd = emalloc(quoted_length); + php_odbc_connstr_quote(pwd, dbh->password, quoted_length); } else { pwd = dbh->password; } diff --git a/main/php_odbc_utils.c b/main/php_odbc_utils.c index d3b18f2bd642..797a4ac04be1 100644 --- a/main/php_odbc_utils.c +++ b/main/php_odbc_utils.c @@ -71,12 +71,20 @@ PHPAPI bool php_odbc_connstr_should_quote(const char *str) } /** - * Estimates the worst-case scenario for a quoted version of a string's size. + * Gets the length of a string after it has been quoted. */ -PHPAPI size_t php_odbc_connstr_estimate_quote_length(const char *in_str) +PHPAPI size_t php_odbc_connstr_get_quoted_length(const char *in_str) { - /* Assume all '}'. Include '{,' '}', and the null terminator too */ - return (strlen(in_str) * 2) + 3; + /* Start with including the quotes ({}) and the null terminator */ + size_t size = 3; + /* Scan the string like strlen, doubling each } character. */ + while (*in_str) { + size++; + if (*in_str++ == '}') { + size++; + } + } + return size; } /** diff --git a/main/php_odbc_utils.h b/main/php_odbc_utils.h index 78353b49a814..f2bc46ebc5ba 100644 --- a/main/php_odbc_utils.h +++ b/main/php_odbc_utils.h @@ -16,5 +16,5 @@ PHPAPI bool php_odbc_connstr_is_quoted(const char *str); PHPAPI bool php_odbc_connstr_should_quote(const char *str); -PHPAPI size_t php_odbc_connstr_estimate_quote_length(const char *in_str); +PHPAPI size_t php_odbc_connstr_get_quoted_length(const char *in_str); PHPAPI size_t php_odbc_connstr_quote(char *out_str, const char *in_str, size_t out_str_size);