From 04323955c1f7724e54d9d70744c28860b565d9a4 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Tue, 4 Nov 2025 19:59:38 +0100 Subject: [PATCH 1/2] pgsql: Fix memory leak when object init fails (#20387) The return value is already overwritten by this point so we do have to clean up the old return value (i.e. dataset) after all. --- ext/pgsql/pgsql.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 241dc214d8e..96a3cc7e707 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -1875,6 +1875,7 @@ static void php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, zend_long result_ ZVAL_COPY_VALUE(&dataset, return_value); 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) { From fcc159b4f623127b054e433d957060835f73c743 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:26:14 +0100 Subject: [PATCH 2/2] Fix GH-20374: PHP with tidy and custom-tags Both enums and integers map to TidyInteger, however, in the TidyInteger case we always used zval_get_long(). So for a non-numeric string, this would get turned into 0. 0 is the first enum value in that case, so the wrong enum value could be selected. To solve this, add special handling for strings and (stringable) objects such that we can explicitly check for numeric strings, and if they're not, handle them as normal strings instead of as 0. Closes GH-20387. --- NEWS | 2 + ext/tidy/tests/gh20374.phpt | 169 ++++++++++++++++++++++++++++++++++++ ext/tidy/tidy.c | 35 +++++++- 3 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 ext/tidy/tests/gh20374.phpt diff --git a/NEWS b/NEWS index 016342ca97d..7cf9b1dde34 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,8 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.3.29 +- Tidy: + . Fixed bug GH-20374 (PHP with tidy and custom-tags). (ndossche) 20 Nov 2025, PHP 8.3.28 diff --git a/ext/tidy/tests/gh20374.phpt b/ext/tidy/tests/gh20374.phpt new file mode 100644 index 00000000000..b17706f7a27 --- /dev/null +++ b/ext/tidy/tests/gh20374.phpt @@ -0,0 +1,169 @@ +--TEST-- +GH-20374 (PHP with tidy and custom-tags) +--EXTENSIONS-- +tidy +--CREDITS-- +franck-paul +--FILE-- +ret; + } +} + +class MyThrowingStringable { + public function __toString(): string { + throw new Error('no'); + } +} + +$values = [ + 'string blocklevel' => 'blocklevel', + 'int' => 1, + 'double overflow' => (string) (2.0**80.0), + 'numeric string int 1' => '1', + 'numeric string double 1.0' => '1.0', + 'false' => false, + 'true' => true, + 'NAN' => NAN, + 'INF' => INF, + 'object with numeric string int 0' => new MyStringable('0'), + 'object with string blocklevel' => new MyStringable('blocklevel'), + 'object with string empty' => new MyStringable('empty'), + 'object with exception' => new MyThrowingStringable, +]; + +foreach ($values as $key => $value) { + echo "--- $key ---\n"; + $str = 'test'; + + $config = [ + 'custom-tags' => $value, + ]; + + $tidy = new tidy(); + try { + $tidy->parseString($str, $config, 'utf8'); + echo $tidy->value, "\n"; + } catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; + } +} + +?> +--EXPECT-- +--- string blocklevel --- + + + + + +test + + +--- int --- + + + + + +test + + +--- double overflow --- + + + + + +test + + +--- numeric string int 1 --- + + + + + +test + + +--- numeric string double 1.0 --- + + + + + +test + + +--- false --- + + + + + +test + + +--- true --- + + + + + +test + + +--- NAN --- + + + + + +test + + +--- INF --- + + + + + +test + + +--- object with numeric string int 0 --- + + + + + +test + + +--- object with string blocklevel --- + + + + + +test + + +--- object with string empty --- + + + + + + +test + + +--- object with exception --- +Error: no diff --git a/ext/tidy/tidy.c b/ext/tidy/tidy.c index 434d5a8493b..2cbbcbeae65 100644 --- a/ext/tidy/tidy.c +++ b/ext/tidy/tidy.c @@ -251,10 +251,37 @@ static int _php_tidy_set_tidy_opt(TidyDoc doc, char *optname, zval *value) zend_tmp_string_release(tmp_str); break; - case TidyInteger: - lval = zval_get_long(value); - if (tidyOptSetInt(doc, tidyOptGetId(opt), lval)) { - return SUCCESS; + case TidyInteger: /* integer or enum */ + ZVAL_DEREF(value); + /* Enum will correspond to a non-numeric string or object */ + if (Z_TYPE_P(value) == IS_STRING || Z_TYPE_P(value) == IS_OBJECT) { + double dval; + str = zval_try_get_tmp_string(value, &tmp_str); + if (UNEXPECTED(!str)) { + return FAILURE; + } + uint8_t type = is_numeric_string(ZSTR_VAL(str), ZSTR_LEN(str), &lval, &dval, true); + if (type == IS_DOUBLE) { + lval = zend_dval_to_lval_cap(dval); + type = IS_LONG; + } + if (type == IS_LONG) { + if (tidyOptSetInt(doc, tidyOptGetId(opt), lval)) { + zend_tmp_string_release(tmp_str); + return SUCCESS; + } + } else { + if (tidyOptSetValue(doc, tidyOptGetId(opt), ZSTR_VAL(str))) { + zend_tmp_string_release(tmp_str); + return SUCCESS; + } + } + zend_tmp_string_release(tmp_str); + } else { + lval = zval_get_long(value); + if (tidyOptSetInt(doc, tidyOptGetId(opt), lval)) { + return SUCCESS; + } } break;