From 0beb2a99d3beb586a1ee5a2c768e43a9218be2a1 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 9 Sep 2026 16:08:18 +0100 Subject: [PATCH 1/4] Zend: deprecate using "_" as a class and constant name (#23615) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RFC: https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_using_as_a_constant_and_compile_time_alias Co-authored-by: Tim Düsterhus --- .../underscore/class_alias_underscore.phpt | 14 ++++++++++ .../underscore}/class_underscore_as_name.phpt | 0 .../class_underscore_as_name_use.phpt | 18 ++++++++++++ .../constant_underscore_as_name.phpt | 28 +++++++++++++++++++ .../constant_underscore_as_name_use.phpt | 18 ++++++++++++ .../function_underscore_as_name.phpt | 23 +++++++++++++++ .../function_underscore_as_name_use.phpt | 18 ++++++++++++ Zend/zend_compile.c | 16 ++++++++++- Zend/zend_constants.c | 6 ++++ 9 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/deprecated_identifiers/underscore/class_alias_underscore.phpt rename Zend/tests/{ => deprecated_identifiers/underscore}/class_underscore_as_name.phpt (100%) create mode 100644 Zend/tests/deprecated_identifiers/underscore/class_underscore_as_name_use.phpt create mode 100644 Zend/tests/deprecated_identifiers/underscore/constant_underscore_as_name.phpt create mode 100644 Zend/tests/deprecated_identifiers/underscore/constant_underscore_as_name_use.phpt create mode 100644 Zend/tests/deprecated_identifiers/underscore/function_underscore_as_name.phpt create mode 100644 Zend/tests/deprecated_identifiers/underscore/function_underscore_as_name_use.phpt diff --git a/Zend/tests/deprecated_identifiers/underscore/class_alias_underscore.phpt b/Zend/tests/deprecated_identifiers/underscore/class_alias_underscore.phpt new file mode 100644 index 000000000000..23afcbfeb92e --- /dev/null +++ b/Zend/tests/deprecated_identifiers/underscore/class_alias_underscore.phpt @@ -0,0 +1,14 @@ +--TEST-- +Using "_" as a class name alias is deprecated +--FILE-- + +--EXPECTF-- +Deprecated: Using "_" as a class alias is deprecated since 8.4 in %s on line %d + +Deprecated: Using "_" as a class alias is deprecated since 8.4 in %s on line %d diff --git a/Zend/tests/class_underscore_as_name.phpt b/Zend/tests/deprecated_identifiers/underscore/class_underscore_as_name.phpt similarity index 100% rename from Zend/tests/class_underscore_as_name.phpt rename to Zend/tests/deprecated_identifiers/underscore/class_underscore_as_name.phpt diff --git a/Zend/tests/deprecated_identifiers/underscore/class_underscore_as_name_use.phpt b/Zend/tests/deprecated_identifiers/underscore/class_underscore_as_name_use.phpt new file mode 100644 index 000000000000..685250b369ac --- /dev/null +++ b/Zend/tests/deprecated_identifiers/underscore/class_underscore_as_name_use.phpt @@ -0,0 +1,18 @@ +--TEST-- +Using "_" as a class name in use statements is deprecated +--FILE-- + +--EXPECTF-- +Deprecated: Using "_" as a class name is deprecated in %s on line %d + +Deprecated: Using "_" as a class name is deprecated in %s on line %d diff --git a/Zend/tests/deprecated_identifiers/underscore/constant_underscore_as_name.phpt b/Zend/tests/deprecated_identifiers/underscore/constant_underscore_as_name.phpt new file mode 100644 index 000000000000..4859e8ca5aeb --- /dev/null +++ b/Zend/tests/deprecated_identifiers/underscore/constant_underscore_as_name.phpt @@ -0,0 +1,28 @@ +--TEST-- +Using "_" as a constant name is deprecated +--FILE-- + +--EXPECTF-- +Deprecated: Calling a constant "_" is deprecated since 8.6 in %s on line %d + +Deprecated: Calling a constant "_" is deprecated since 8.6 in %s on line %d + +Deprecated: Calling a constant "_" is deprecated since 8.6 in %s on line %d + +Warning: Constant _ already defined, this will be an error in PHP 9 in %s on line %d + +Deprecated: Calling a constant "_" is deprecated since 8.6 in %s on line %d diff --git a/Zend/tests/deprecated_identifiers/underscore/constant_underscore_as_name_use.phpt b/Zend/tests/deprecated_identifiers/underscore/constant_underscore_as_name_use.phpt new file mode 100644 index 000000000000..e7421318aead --- /dev/null +++ b/Zend/tests/deprecated_identifiers/underscore/constant_underscore_as_name_use.phpt @@ -0,0 +1,18 @@ +--TEST-- +Using "_" as a constant in use statements is deprecated +--FILE-- + +--EXPECTF-- +Deprecated: Using "_" as a constant name is deprecated since 8.6 in %s on line %d + +Deprecated: Using "_" as a constant name is deprecated since 8.6 in %s on line %d diff --git a/Zend/tests/deprecated_identifiers/underscore/function_underscore_as_name.phpt b/Zend/tests/deprecated_identifiers/underscore/function_underscore_as_name.phpt new file mode 100644 index 000000000000..0d127fab6300 --- /dev/null +++ b/Zend/tests/deprecated_identifiers/underscore/function_underscore_as_name.phpt @@ -0,0 +1,23 @@ +--TEST-- +Using "_" as a function name is NOT deprecated +--SKIPIF-- + +--FILE-- + +--EXPECT-- +OK diff --git a/Zend/tests/deprecated_identifiers/underscore/function_underscore_as_name_use.phpt b/Zend/tests/deprecated_identifiers/underscore/function_underscore_as_name_use.phpt new file mode 100644 index 000000000000..688683a60396 --- /dev/null +++ b/Zend/tests/deprecated_identifiers/underscore/function_underscore_as_name_use.phpt @@ -0,0 +1,18 @@ +--TEST-- +Using "_" as a function in use statements is NOT deprecated +--FILE-- + +--EXPECT-- +OK diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 317114265c57..5aa1b07f304d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -252,7 +252,7 @@ void zend_assert_valid_class_name(const zend_string *name, const char *type) /* zend_error_noreturn(E_COMPILE_ERROR, "Cannot use \"%s\" as %s as it is reserved", ZSTR_VAL(name), type); } - if (zend_string_equals_literal(name, "_")) { + if (zend_string_equals_literal(name, "_") || zend_string_ends_with_literal(name, "\\_")) { zend_error(E_DEPRECATED, "Using \"_\" as %s is deprecated since 8.4", type); } } @@ -10213,6 +10213,20 @@ static void zend_compile_use(zend_ast *ast) /* {{{ */ "is a special class name", ZSTR_VAL(old_name), ZSTR_VAL(new_name), ZSTR_VAL(new_name)); } + if (zend_string_equals(new_name, ZSTR_CHAR('_'))) { + switch (type) { + case ZEND_SYMBOL_CLASS: + zend_error(E_DEPRECATED, "Using \"_\" as a class name is deprecated"); + break; + case ZEND_SYMBOL_CONST: + zend_error(E_DEPRECATED, "Using \"_\" as a constant name is deprecated since 8.6"); + break; + case ZEND_SYMBOL_FUNCTION: + break; + default: ZEND_UNREACHABLE(); + } + } + if (current_ns) { zend_string *ns_name = zend_string_alloc(ZSTR_LEN(current_ns) + 1 + ZSTR_LEN(new_name), 0); zend_str_tolower_copy(ZSTR_VAL(ns_name), ZSTR_VAL(current_ns), ZSTR_LEN(current_ns)); diff --git a/Zend/zend_constants.c b/Zend/zend_constants.c index 18292203bee9..c8a2ae62d756 100644 --- a/Zend/zend_constants.c +++ b/Zend/zend_constants.c @@ -538,6 +538,12 @@ ZEND_API zend_constant *zend_register_constant(zend_constant *c) c->attributes = NULL; + if ( + zend_string_equals(name, ZSTR_CHAR('_')) + || (slash && zend_string_ends_with_literal(name, "\\_")) + ) { + zend_error(E_DEPRECATED, "Calling a constant \"_\" is deprecated since 8.6"); + } /* Check if the user is trying to define any special constant */ if (zend_string_equals_literal(name, "__COMPILER_HALT_OFFSET__") || (!persistent && zend_get_special_const(ZSTR_VAL(name), ZSTR_LEN(name))) From 704b00cd76930e639a9e42766a5b4f6466100910 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Thu, 10 Sep 2026 00:02:52 +0800 Subject: [PATCH 2/4] Zend: Fix iterator relocation at the current element during rehash (#23607) When one iterator points at a hole and another at the next live bucket, the relocation loop must include the iterator at that bucket's original position. Otherwise it is moved to the following bucket's destination, causing a nested by-reference foreach to skip an element. Add regression coverage for packed-to-hash conversion and compaction of a full mixed table. --- NEWS | 2 + .../array_dup_internal_pointer_hole.phpt | 45 +++++++++++++++++++ Zend/tests/array_dup_iterator_past_end.phpt | 23 ++++++++++ Zend/tests/array_dup_multiple_iterators.phpt | 38 ++++++++++++++++ Zend/tests/rehash_multiple_iterators.phpt | 40 +++++++++++++++++ Zend/zend_hash.c | 10 +++-- 6 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 Zend/tests/array_dup_internal_pointer_hole.phpt create mode 100644 Zend/tests/array_dup_iterator_past_end.phpt create mode 100644 Zend/tests/array_dup_multiple_iterators.phpt create mode 100644 Zend/tests/rehash_multiple_iterators.phpt diff --git a/NEWS b/NEWS index 3346d38ea898..365cd24de846 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,8 @@ PHP NEWS - Core: . Fixed out-of-bounds reads during automatic UTF-16/32 encoding detection. (Yudai Takada) + . Fixed incorrect internal pointer and foreach iterator positions when + compacting arrays with holes. (Weilin Du) . Fixed bug GH-15375 (Nested "yield from" skips items after a valid() or next() call on the inner generator). (iliaal) . Fixed bug GH-23232 (lone namespace separator asks the autoloader for an diff --git a/Zend/tests/array_dup_internal_pointer_hole.phpt b/Zend/tests/array_dup_internal_pointer_hole.phpt new file mode 100644 index 000000000000..97fdd40bb1ad --- /dev/null +++ b/Zend/tests/array_dup_internal_pointer_hole.phpt @@ -0,0 +1,45 @@ +--TEST-- +Array duplication relocates an internal pointer on a hole, with and without foreach iterators +--FILE-- +', current($values), "\n"; + next($values); + echo 'next: ', key($values), '=>', current($values), "\n"; + echo 'copy current: ', key($copy), '=>', current($copy), "\n"; + echo 'copy keys: ', implode(' ', array_keys($copy)), "\n"; +} + +foreach ([false, true] as $withIterator) { + echo $withIterator ? "With iterator:\n" : "Without iterator:\n"; + $values = ['a' => 10, 'b' => 11, 'c' => 12, 'd' => 13, + 'e' => 14, 'f' => 15, 'g' => 16, 'h' => 17]; + next($values); + next($values); + // Leave the internal pointer on a hole before several surviving elements. + unset($values['a'], $values['b'], $values['c'], $values['d']); + + if ($withIterator) { + foreach ($values as &$value) { + duplicate($values); + break; + } + unset($value); + } else { + duplicate($values); + } +} +?> +--EXPECT-- +Without iterator: +current: e=>14 +next: f=>15 +copy current: e=>14 +copy keys: e f g h +With iterator: +current: e=>14 +next: f=>15 +copy current: e=>14 +copy keys: e f g h diff --git a/Zend/tests/array_dup_iterator_past_end.phpt b/Zend/tests/array_dup_iterator_past_end.phpt new file mode 100644 index 000000000000..80e52e3e5ab2 --- /dev/null +++ b/Zend/tests/array_dup_iterator_past_end.phpt @@ -0,0 +1,23 @@ +--TEST-- +Array duplication preserves past-the-end iterators when compacting holes +--FILE-- + 10, 'b' => 11, 'c' => 12, 'd' => 13]; +unset($values['a'], $values['b']); + +foreach ($values as $key => &$value) { + echo "$key=>$value\n"; + if ($key === 'd') { + // The iterator is one past the end; COW compacts the preceding holes. + $copy = $values; + $values['e'] = 14; + } +} +unset($value); +echo 'copy: ', implode(' ', array_keys($copy)), "\n"; +?> +--EXPECT-- +c=>12 +d=>13 +e=>14 +copy: c d diff --git a/Zend/tests/array_dup_multiple_iterators.phpt b/Zend/tests/array_dup_multiple_iterators.phpt new file mode 100644 index 000000000000..96f2dbb7f8de --- /dev/null +++ b/Zend/tests/array_dup_multiple_iterators.phpt @@ -0,0 +1,38 @@ +--TEST-- +Array duplication updates iterators at both a hole and the next defined element +--FILE-- + &$outerValue) { + $outerVisits[] = "$outerKey=>$outerValue"; + if ($first) { + $first = false; + foreach ($values as $innerKey => &$innerValue) { + $innerVisits[] = "$innerKey=>$innerValue"; + if ($innerValue === 11) { + // The outer cursor is at a hole, the inner at the next value. + unset($values['a'], $values['b']); + $copy = $values; + // Trigger copy-on-write duplication, which compacts the holes. + $values['i'] = 18; + } + } + unset($innerValue); + } + } + unset($outerValue); + echo 'outer: ', implode(' ', $outerVisits), "\n"; + echo 'inner: ', implode(' ', $innerVisits), "\n"; + echo 'copy: ', implode(' ', array_keys($copy)), "\n"; +} + +test(['a' => 10, 'b' => 11, 'c' => 12, 'd' => 13, + 'e' => 14, 'f' => 15, 'g' => 16, 'h' => 17]); +?> +--EXPECT-- +outer: a=>10 c=>12 d=>13 e=>14 f=>15 g=>16 h=>17 i=>18 +inner: a=>10 b=>11 c=>12 d=>13 e=>14 f=>15 g=>16 h=>17 i=>18 +copy: c d e f g h diff --git a/Zend/tests/rehash_multiple_iterators.phpt b/Zend/tests/rehash_multiple_iterators.phpt new file mode 100644 index 000000000000..cbbfc75d703a --- /dev/null +++ b/Zend/tests/rehash_multiple_iterators.phpt @@ -0,0 +1,40 @@ +--TEST-- +Rehashing updates iterators at both a hole and the next defined element +--FILE-- + &$outerValue) { + $outerVisits[] = "$outerKey=>$outerValue"; + if ($first) { + $first = false; + foreach ($values as $innerKey => &$innerValue) { + $innerVisits[] = "$innerKey=>$innerValue"; + if ($innerValue === 11) { + // The outer cursor is at a hole, the inner at the next value. + unset($values[$firstKey], $values[$secondKey]); + $values[$newKey] = $newValue; + } + } + unset($innerValue); + } + } + unset($outerValue); + echo 'outer: ', implode(' ', $outerVisits), "\n"; + echo 'inner: ', implode(' ', $innerVisits), "\n"; +} + +// Adding a string key converts packed storage and compacts its holes. +test([10, 11, 12, 13, 14], 0, 1, 'new', 15); + +// Inserting into a full mixed table compacts its holes without growing it. +test(['a' => 10, 'b' => 11, 'c' => 12, 'd' => 13, + 'e' => 14, 'f' => 15, 'g' => 16, 'h' => 17], 'a', 'b', 'i', 18); +?> +--EXPECT-- +outer: 0=>10 2=>12 3=>13 4=>14 new=>15 +inner: 0=>10 1=>11 2=>12 3=>13 4=>14 new=>15 +outer: a=>10 c=>12 d=>13 e=>14 f=>15 g=>16 h=>17 i=>18 +inner: a=>10 b=>11 c=>12 d=>13 e=>14 f=>15 g=>16 h=>17 i=>18 diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index 82d0318428fa..b4de0d7b4130 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -1412,7 +1412,7 @@ ZEND_API void ZEND_FASTCALL zend_hash_rehash(HashTable *ht) do { zend_hash_iterators_update(ht, iter_pos, j); iter_pos = zend_hash_iterators_lower_pos(ht, iter_pos + 1); - } while (iter_pos < i); + } while (iter_pos <= i); } q++; j++; @@ -2408,7 +2408,7 @@ static zend_always_inline uint32_t zend_array_dup_elements(HashTable *source, Ha if (EXPECTED(!HT_HAS_ITERATORS(target))) { while (p != end) { if (zend_array_dup_element(source, target, target_idx, p, q, 0, static_keys, with_holes)) { - if (source->nInternalPointer == idx) { + if (UNEXPECTED(target->nInternalPointer > target_idx && target->nInternalPointer <= idx)) { target->nInternalPointer = target_idx; } target_idx++; q++; @@ -2421,19 +2421,21 @@ static zend_always_inline uint32_t zend_array_dup_elements(HashTable *source, Ha while (p != end) { if (zend_array_dup_element(source, target, target_idx, p, q, 0, static_keys, with_holes)) { - if (source->nInternalPointer == idx) { + if (UNEXPECTED(target->nInternalPointer > target_idx && target->nInternalPointer <= idx)) { target->nInternalPointer = target_idx; } if (UNEXPECTED(idx >= iter_pos)) { do { zend_hash_iterators_update(target, iter_pos, target_idx); iter_pos = zend_hash_iterators_lower_pos(target, iter_pos + 1); - } while (iter_pos < idx); + } while (iter_pos <= idx); } target_idx++; q++; } idx++; p++; } + /* Move past-the-end iterators so they can pick up newly appended elements. */ + _zend_hash_iterators_update(target, source->nNumUsed, target_idx); } return target_idx; } From 027581185039942d13edc9e22057c31b4826a741 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Thu, 10 Sep 2026 00:06:26 +0800 Subject: [PATCH 3/4] [skip ci] Fix NEWS entry for #23607 --- NEWS | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 365cd24de846..1fd676d2a649 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.27 +- Core: + . Fixed incorrect internal pointer and foreach iterator positions when + compacting arrays with holes. (Weilin Du) + - DOM: . Fixed use-after-free when re-constructing a DOMXPath whose php:function registrations are freed while still reachable from the cycle collector. @@ -17,8 +21,6 @@ PHP NEWS - Core: . Fixed out-of-bounds reads during automatic UTF-16/32 encoding detection. (Yudai Takada) - . Fixed incorrect internal pointer and foreach iterator positions when - compacting arrays with holes. (Weilin Du) . Fixed bug GH-15375 (Nested "yield from" skips items after a valid() or next() call on the inner generator). (iliaal) . Fixed bug GH-23232 (lone namespace separator asks the autoloader for an From 89687719b40691a773bd57a20147288c2fa278fc Mon Sep 17 00:00:00 2001 From: Louis-Arnaud Date: Wed, 9 Sep 2026 19:51:30 +0200 Subject: [PATCH 4/4] ext/calendar: take zend_long in the *ToSdn() conversion helpers (#23555) GregorianToSdn(), JulianToSdn(), JewishToSdn() and FrenchToSdn() took C int parameters while every PHP_FUNCTION() calling them parses zend_long, so the arguments were implicitly narrowed before any range check ran. Values whose low 32 bits alias into the accepted range slipped past the guards inside the helpers: gregoriantojd(1 + 2**32, 1, 1) returned the Julian Day of month 1 instead of 0. cal_to_jd() and cal_days_in_month() were affected the same way through cal_to_jd_func_t. Widen the parameters so the existing guards see the value the user passed. The typedef's parameter names were also in the wrong order and are fixed to match the callees. JewishToSdn() passes its year on to FindStartOfYear(), whose first two outputs are int, so the year is narrowed once in JewishToSdn() itself, right after the check that leaves both year and year + 1 within int range. FindStartOfYear() keeps its int parameter and stays unaware of bounds its caller enforces. Widening JewishToSdn() additionally removes a signed overflow: `sdn = tishri1 + day - 1` was computed in int, so year 5879542 returned a negative Julian Day. juliantojd_overflow.phpt asserted the aliased result for year 6000000000, which is past the supported maximum enforced since GH-22602 (INT_MAX - 4800); it now returns 0, and the test covers the largest accepted year instead. --- NEWS | 4 ++ ext/calendar/calendar.c | 2 +- ext/calendar/french.c | 6 +- ext/calendar/gregor.c | 8 +-- ext/calendar/jewish.c | 19 ++++--- ext/calendar/julian.c | 8 +-- ext/calendar/sdncal.h | 8 +-- ext/calendar/tests/juliantojd_overflow.phpt | 6 +- ext/calendar/tests/tojd_out_of_int_range.phpt | 57 +++++++++++++++++++ 9 files changed, 93 insertions(+), 25 deletions(-) create mode 100644 ext/calendar/tests/tojd_out_of_int_range.phpt diff --git a/NEWS b/NEWS index 16faf4372015..bacb68f387d0 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,10 @@ PHP NEWS . Fixed bug GH-23242 (PHP development server does not support Expect 100-continue flow control). (Sjoerd Langkemper) +- Calendar: + . Fixed *tojd() functions and cal_to_jd() truncating arguments outside the + int range instead of rejecting them. (lacatoire) + - DOM: . Fixed NamedNodeMap::getNamedItemNS() with an empty URI not matching the null namespace in spec-following mode. (Ilia Alshanetsky) diff --git a/ext/calendar/calendar.c b/ext/calendar/calendar.c index f26d611092db..14daf3a5e678 100644 --- a/ext/calendar/calendar.c +++ b/ext/calendar/calendar.c @@ -41,7 +41,7 @@ enum cal_name_type_t { CAL_NUM_CALS }; -typedef zend_long (*cal_to_jd_func_t) (int month, int day, int year); +typedef zend_long (*cal_to_jd_func_t) (zend_long year, zend_long month, zend_long day); typedef void (*cal_from_jd_func_t) (zend_long jd, int *year, int *month, int *day); typedef char *(*cal_as_string_func_t) (int year, int month, int day); diff --git a/ext/calendar/french.c b/ext/calendar/french.c index 0707e27ed8d1..913098325629 100644 --- a/ext/calendar/french.c +++ b/ext/calendar/french.c @@ -115,9 +115,9 @@ void SdnToFrench( } zend_long FrenchToSdn( - int year, - int month, - int day) + zend_long year, + zend_long month, + zend_long day) { /* check for invalid dates */ if (year < 1 || year > 14 || diff --git a/ext/calendar/gregor.c b/ext/calendar/gregor.c index eaee9c8c73c7..4047051cf3e1 100644 --- a/ext/calendar/gregor.c +++ b/ext/calendar/gregor.c @@ -200,12 +200,12 @@ void SdnToGregorian( } zend_long GregorianToSdn( - int inputYear, - int inputMonth, - int inputDay) + zend_long inputYear, + zend_long inputMonth, + zend_long inputDay) { zend_long year; - int month; + zend_long month; /* check for invalid dates */ if (inputYear == 0 || inputYear < -4714 || diff --git a/ext/calendar/jewish.c b/ext/calendar/jewish.c index b589c4cb2617..c6aec67c8548 100644 --- a/ext/calendar/jewish.c +++ b/ext/calendar/jewish.c @@ -696,9 +696,9 @@ void SdnToJewish( * and compare with the original. */ zend_long JewishToSdn( - int year, - int month, - int day) + zend_long year, + zend_long month, + zend_long day) { zend_long sdn; int metonicCycle; @@ -713,11 +713,14 @@ zend_long JewishToSdn( if (year <= 0 || year >= INT_MAX - 1 || day <= 0 || day > 30) { return (0); } + /* The check above leaves both year and year + 1 within int range. */ + int int_year = (int) year; + switch (month) { case 1: case 2: /* It is Tishri or Heshvan - don't need the year length. */ - FindStartOfYear(year, &metonicCycle, &metonicYear, + FindStartOfYear(int_year, &metonicCycle, &metonicYear, &moladDay, &moladHalakim, &tishri1); if (month == 1) { sdn = tishri1 + day - 1; @@ -730,7 +733,7 @@ zend_long JewishToSdn( /* It is Kislev - must find the year length. */ /* Find the start of the year. */ - FindStartOfYear(year, &metonicCycle, &metonicYear, + FindStartOfYear(int_year, &metonicCycle, &metonicYear, &moladDay, &moladHalakim, &tishri1); /* Find the end of the year. */ @@ -753,10 +756,10 @@ zend_long JewishToSdn( case 6: /* It is Tevet, Shevat or Adar I - don't need the year length. */ - FindStartOfYear(year + 1, &metonicCycle, &metonicYear, + FindStartOfYear(int_year + 1, &metonicCycle, &metonicYear, &moladDay, &moladHalakim, &tishri1After); - if (monthsPerYear[(year - 1) % 19] == 12) { + if (monthsPerYear[(int_year - 1) % 19] == 12) { lengthOfAdarIAndII = 29; } else { lengthOfAdarIAndII = 59; @@ -773,7 +776,7 @@ zend_long JewishToSdn( default: /* It is Adar II or later - don't need the year length. */ - FindStartOfYear(year + 1, &metonicCycle, &metonicYear, + FindStartOfYear(int_year + 1, &metonicCycle, &metonicYear, &moladDay, &moladHalakim, &tishri1After); switch (month) { diff --git a/ext/calendar/julian.c b/ext/calendar/julian.c index c24527941d3e..d539e2b45bd8 100644 --- a/ext/calendar/julian.c +++ b/ext/calendar/julian.c @@ -213,12 +213,12 @@ void SdnToJulian( } zend_long JulianToSdn( - int inputYear, - int inputMonth, - int inputDay) + zend_long inputYear, + zend_long inputMonth, + zend_long inputDay) { zend_long year; - int month; + zend_long month; /* check for invalid dates */ if (inputYear == 0 || inputYear < -4713 || diff --git a/ext/calendar/sdncal.h b/ext/calendar/sdncal.h index fa3aa7c641a9..17202791c439 100644 --- a/ext/calendar/sdncal.h +++ b/ext/calendar/sdncal.h @@ -69,17 +69,17 @@ /* Gregorian calendar conversions. */ void SdnToGregorian(zend_long sdn, int *pYear, int *pMonth, int *pDay); -zend_long GregorianToSdn(int year, int month, int day); +zend_long GregorianToSdn(zend_long year, zend_long month, zend_long day); extern const char * const MonthNameShort[13]; extern const char * const MonthNameLong[13]; /* Julian calendar conversions. */ void SdnToJulian(zend_long sdn, int *pYear, int *pMonth, int *pDay); -zend_long JulianToSdn(int year, int month, int day); +zend_long JulianToSdn(zend_long year, zend_long month, zend_long day); /* Jewish calendar conversions. */ void SdnToJewish(zend_long sdn, int *pYear, int *pMonth, int *pDay); -zend_long JewishToSdn(int year, int month, int day); +zend_long JewishToSdn(zend_long year, zend_long month, zend_long day); extern const char * const JewishMonthName[14]; extern const char * const JewishMonthNameLeap[14]; extern const char * const JewishMonthHebName[14]; @@ -88,7 +88,7 @@ extern const int monthsPerYear[19]; /* French republic calendar conversions. */ void SdnToFrench(zend_long sdn, int *pYear, int *pMonth, int *pDay); -zend_long FrenchToSdn(int inputYear, int inputMonth, int inputDay); +zend_long FrenchToSdn(zend_long inputYear, zend_long inputMonth, zend_long inputDay); extern const char * const FrenchMonthName[14]; /* Islamic calendar conversions. */ diff --git a/ext/calendar/tests/juliantojd_overflow.phpt b/ext/calendar/tests/juliantojd_overflow.phpt index cdf1b203d96c..3422c6c7268d 100644 --- a/ext/calendar/tests/juliantojd_overflow.phpt +++ b/ext/calendar/tests/juliantojd_overflow.phpt @@ -6,7 +6,11 @@ calendar --FILE-- --EXPECT-- -622764916319 +784368370049 +0 diff --git a/ext/calendar/tests/tojd_out_of_int_range.phpt b/ext/calendar/tests/tojd_out_of_int_range.phpt new file mode 100644 index 000000000000..aeb6b9c4b5c8 --- /dev/null +++ b/ext/calendar/tests/tojd_out_of_int_range.phpt @@ -0,0 +1,57 @@ +--TEST-- +*tojd(): arguments outside the int range must not alias into the accepted range +--EXTENSIONS-- +calendar +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; + } +} + +// cal_to_jd() and cal_days_in_month() reach the same helpers through +// cal_to_jd_func_t, and have no lower bound on $year of their own. +echo "cal_to_jd\n"; +var_dump(cal_to_jd(CAL_GREGORIAN, 1, 1, 1 - $bias)); +try { + var_dump(cal_days_in_month(CAL_GREGORIAN, 1, 1 - $bias)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} +?> +--EXPECT-- +gregoriantojd +int(1721426) +int(0) +int(0) +int(0) +juliantojd +int(1721424) +int(0) +int(0) +int(0) +jewishtojd +int(347998) +int(0) +int(0) +ValueError: jewishtojd(): Argument #3 ($year) must be between -2147483648 and 2147483647 +frenchtojd +int(2375840) +int(0) +int(0) +int(0) +cal_to_jd +int(0) +ValueError: Invalid date