Commit f3c50bfb1bc for php.net

commit f3c50bfb1bc032758b1af2e30776abf1a18c866d
Merge: 5df586300ab 2d86f8cf489
Author: Ilija Tovilo <ilija.tovilo@me.com>
Date:   Wed Jul 15 20:39:51 2026 +0200

    Merge branch 'PHP-8.2' into PHP-8.3

    * PHP-8.2:
      Fix leak on double DatePeriod::__construct() call

diff --cc NEWS
index 3bebd2660a2,e9e5b76cea6..03a3f4f89f7
--- a/NEWS
+++ b/NEWS
@@@ -1,13 -1,11 +1,15 @@@
  PHP                                                                        NEWS
  |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
 -?? ??? ????, PHP 8.2.33
 +?? ??? ????, PHP 8.3.33

+ - Date:
+   . Fixed leak on double DatePeriod::__construct() call. (ilutov)

 -02 Jul 2026, PHP 8.2.32
 +02 Jul 2026, PHP 8.3.32
 +
 +- Streams:
 +  . Fixed bug GH-21468 (Segfault in file_get_contents w/ a https URL
 +    and a proxy set). (CVE-2026-12184) (ndossche)

  - OpenSSL:
    . Fixed bug GH-22187 (Memory corruption (zend_mm_heap corrupted) in
diff --cc ext/date/php_date.c
index 84fad72948f,c71cef2cf15..a74317bd95c
--- a/ext/date/php_date.c
+++ b/ext/date/php_date.c
@@@ -4883,103 -4720,23 +4883,120 @@@ static bool date_period_initialize(time
  	return retval;
  } /* }}} */

 +static bool date_period_init_iso8601_string(php_period_obj *dpobj, zend_class_entry* base_ce, char *isostr, size_t isostr_len, zend_long options, zend_long *recurrences)
 +{
 +	if (!date_period_initialize(&(dpobj->start), &(dpobj->end), &(dpobj->interval), recurrences, isostr, isostr_len)) {
 +		return false;
 +	}
 +
 +	if (dpobj->start == NULL) {
 +		zend_string *func = get_active_function_or_method_name();
 +		zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): ISO interval must contain a start date, \"%s\" given", ZSTR_VAL(func), isostr);
 +		zend_string_release(func);
 +		return false;
 +	}
 +	if (dpobj->interval == NULL) {
 +		zend_string *func = get_active_function_or_method_name();
 +		zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): ISO interval must contain an interval, \"%s\" given", ZSTR_VAL(func), isostr);
 +		zend_string_release(func);
 +		return false;
 +	}
 +	if (dpobj->end == NULL && recurrences == 0) {
 +		zend_string *func = get_active_function_or_method_name();
 +		zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): ISO interval must contain an end date or a recurrence count, \"%s\" given", ZSTR_VAL(func), isostr);
 +		zend_string_release(func);
 +		return false;
 +	}
 +
 +	if (dpobj->start) {
 +		timelib_update_ts(dpobj->start, NULL);
 +	}
 +	if (dpobj->end) {
 +		timelib_update_ts(dpobj->end, NULL);
 +	}
 +	dpobj->start_ce = base_ce;
 +
 +	return true;
 +}
 +
 +static bool date_period_init_finish(php_period_obj *dpobj, zend_long options, zend_long recurrences)
 +{
 +	const zend_long max_recurrences = (INT_MAX - 8);
 +
 +	if (dpobj->end == NULL && (recurrences < 1 || recurrences > max_recurrences)) {
 +		zend_string *func = get_active_function_or_method_name();
 +		zend_throw_exception_ex(date_ce_date_malformed_period_string_exception, 0, "%s(): Recurrence count must be greater or equal to 1 and lower than " ZEND_LONG_FMT, ZSTR_VAL(func), max_recurrences + 1);
 +		zend_string_release(func);
 +		return false;
 +	}
 +
 +	/* options */
 +	dpobj->include_start_date = !(options & PHP_DATE_PERIOD_EXCLUDE_START_DATE);
 +	dpobj->include_end_date = options & PHP_DATE_PERIOD_INCLUDE_END_DATE;
 +
 +	/* recurrences */
 +	recurrences += dpobj->include_start_date + dpobj->include_end_date;
 +
 +	if (UNEXPECTED(recurrences > max_recurrences)) {
 +		zend_string *func = get_active_function_or_method_name();
 +		zend_throw_exception_ex(date_ce_date_malformed_string_exception, 0, "%s(): Recurrence count must be greater or equal to 1 and lower than " ZEND_LONG_FMT " (including options)", ZSTR_VAL(func), max_recurrences + 1);
 +		zend_string_release(func);
 +		return false;
 +	}
 +
 +	dpobj->recurrences = (int)recurrences;
 +
 +	dpobj->initialized = 1;
 +
 +	initialize_date_period_properties(dpobj);
 +
 +	return true;
 +}
 +
 +PHP_METHOD(DatePeriod, createFromISO8601String)
 +{
 +	php_period_obj *dpobj;
 +	zend_long recurrences = 0, options = 0;
 +	char *isostr = NULL;
 +	size_t isostr_len = 0;
 +
 +	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|l", &isostr, &isostr_len, &options) == FAILURE) {
 +		RETURN_THROWS();
 +	}
 +
 +	if (object_init_ex(return_value, execute_data->This.value.ce ? execute_data->This.value.ce : date_ce_period) != SUCCESS) {
 +		RETURN_THROWS();
 +	}
 +	dpobj = Z_PHPPERIOD_P(return_value);
 +
 +	dpobj->current = NULL;
 +
 +	if (!date_period_init_iso8601_string(dpobj, date_ce_immutable, isostr, isostr_len, options, &recurrences)) {
 +		RETURN_THROWS();
 +	}
 +
 +	if (!date_period_init_finish(dpobj, options, recurrences)) {
 +		RETURN_THROWS();
 +	}
 +}
 +
+ 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)
  {
@@@ -5001,18 -4758,46 +5018,19 @@@
  	}

  	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)) {
 -			RETURN_THROWS();
 -		}
 -
 -		if (dpobj->start == NULL) {
 -			zend_string *func = get_active_function_or_method_name();
 -			zend_throw_exception_ex(NULL, 0, "%s(): ISO interval must contain a start date, \"%s\" given", ZSTR_VAL(func), isostr);
 -			zend_string_release(func);
 -			RETURN_THROWS();
 -		}
 -		if (dpobj->interval == NULL) {
 -			zend_string *func = get_active_function_or_method_name();
 -			zend_throw_exception_ex(NULL, 0, "%s(): ISO interval must contain an interval, \"%s\" given", ZSTR_VAL(func), isostr);
 -			zend_string_release(func);
 +		if (!date_period_init_iso8601_string(dpobj, date_ce_date, isostr, isostr_len, options, &recurrences)) {
  			RETURN_THROWS();
  		}
 -		if (dpobj->end == NULL && recurrences == 0) {
 -			zend_string *func = get_active_function_or_method_name();
 -			zend_throw_exception_ex(NULL, 0, "%s(): ISO interval must contain an end date or a recurrence count, \"%s\" given", ZSTR_VAL(func), isostr);
 -			zend_string_release(func);
 -			RETURN_THROWS();
 -		}
 -
 -		if (dpobj->start) {
 -			timelib_update_ts(dpobj->start, NULL);
 -		}
 -		if (dpobj->end) {
 -			timelib_update_ts(dpobj->end, NULL);
 -		}
 -		dpobj->start_ce = date_ce_date;
  	} else {
  		/* check initialisation */
 -		DATE_CHECK_INITIALIZED(Z_PHPDATE_P(start)->time, DateTimeInterface);
 +		DATE_CHECK_INITIALIZED(Z_PHPDATE_P(start)->time, date_ce_interface);
  		if (end) {
 -			DATE_CHECK_INITIALIZED(Z_PHPDATE_P(end)->time, DateTimeInterface);
 +			DATE_CHECK_INITIALIZED(Z_PHPDATE_P(end)->time, date_ce_interface);
  		}
+ 		DATE_CHECK_INITIALIZED(Z_PHPINTERVAL_P(interval)->initialized, Z_OBJCE_P(interval));

  		/* init */
  		php_interval_obj *intobj = Z_PHPINTERVAL_P(interval);