diff --git a/NEWS b/NEWS index 7c9c4f08ed1b..bacb68f387d0 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,10 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.6.0RC1 +- 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. @@ -26,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/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/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/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_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))) diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index 84751a02e7c0..4640846ecc76 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -1420,7 +1420,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++; @@ -2416,7 +2416,7 @@ static zend_always_inline uint32_t zend_array_dup_elements(const HashTable *sour if (EXPECTED(!HT_HAS_ITERATORS(target))) { while (p != end) { if (zend_array_dup_element(source, target, target_idx, p, q, false, static_keys, with_holes)) { - if (source->nInternalPointer == idx) { + if (UNEXPECTED(target->nInternalPointer > target_idx && target->nInternalPointer <= idx)) { target->nInternalPointer = target_idx; } target_idx++; q++; @@ -2429,19 +2429,21 @@ static zend_always_inline uint32_t zend_array_dup_elements(const HashTable *sour while (p != end) { if (zend_array_dup_element(source, target, target_idx, p, q, false, 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; } 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