diff --git a/NEWS b/NEWS index 370095987ea7..e476e2b83036 100644 --- a/NEWS +++ b/NEWS @@ -55,6 +55,9 @@ PHP NEWS - Zip: . Fixed bug GH-21705 (ZipArchive::getFromIndex() ignores ZipArchive::FL_UNCHANGED for deleted entries). (Weilin Du) + . Fixed bug GH-22176 (memory leak with ZipArchive::registerCancelBack() + is used with reference returning function during shutdown). + (David Carlier) 02 Jul 2026, PHP 8.6.0alpha1 diff --git a/ext/intl/breakiterator/codepointiterator_internal.cpp b/ext/intl/breakiterator/codepointiterator_internal.cpp index 47d5df00c1be..0604cfa24167 100644 --- a/ext/intl/breakiterator/codepointiterator_internal.cpp +++ b/ext/intl/breakiterator/codepointiterator_internal.cpp @@ -126,7 +126,7 @@ void CodePointBreakIterator::setText(const UnicodeString &text) void CodePointBreakIterator::setText(UText *text, UErrorCode &status) { - if (U_FAILURE(status)) { + if (UNEXPECTED(U_FAILURE(status))) { return; } @@ -234,7 +234,7 @@ CodePointBreakIterator *CodePointBreakIterator::createBufferClone( void *stackBuffer, int32_t &bufferSize, UErrorCode &status) { //see implementation of RuleBasedBreakIterator::createBufferClone() - if (U_FAILURE(status)) { + if (UNEXPECTED(U_FAILURE(status))) { return NULL; } @@ -273,17 +273,17 @@ CodePointBreakIterator *CodePointBreakIterator::createBufferClone( CodePointBreakIterator &CodePointBreakIterator::refreshInputText(UText *input, UErrorCode &status) { //see implementation of RuleBasedBreakIterator::createBufferClone() - if (U_FAILURE(status)) { + if (UNEXPECTED(U_FAILURE(status))) { return *this; } - if (input == NULL) { + if (UNEXPECTED(input == NULL)) { status = U_ILLEGAL_ARGUMENT_ERROR; return *this; } int64_t pos = utext_getNativeIndex(this->fText); this->fText = utext_clone(this->fText, input, false, true, &status); - if (U_FAILURE(status)) { + if (UNEXPECTED(U_FAILURE(status))) { return *this; } diff --git a/ext/intl/breakiterator/rulebasedbreakiterator_methods.cpp b/ext/intl/breakiterator/rulebasedbreakiterator_methods.cpp index 1e1c2f0f6e5c..4526b9faab13 100644 --- a/ext/intl/breakiterator/rulebasedbreakiterator_methods.cpp +++ b/ext/intl/breakiterator/rulebasedbreakiterator_methods.cpp @@ -57,7 +57,7 @@ U_CFUNC PHP_METHOD(IntlRuleBasedBreakIterator, __construct) if (!compiled) { UnicodeString rulesStr; UParseError parseError = UParseError(); - if (intl_stringFromChar(rulesStr, ZSTR_VAL(rules), ZSTR_LEN(rules), &status) == FAILURE) { + if (UNEXPECTED(intl_stringFromChar(rulesStr, ZSTR_VAL(rules), ZSTR_LEN(rules), &status) == FAILURE)) { zend_throw_exception(IntlException_ce_ptr, "IntlRuleBasedBreakIterator::__construct(): rules were not a valid UTF-8 string", 0); RETURN_THROWS(); @@ -65,7 +65,7 @@ U_CFUNC PHP_METHOD(IntlRuleBasedBreakIterator, __construct) rbbi = new RuleBasedBreakIterator(rulesStr, parseError, status); intl_error_set_code(NULL, status); - if (U_FAILURE(status)) { + if (UNEXPECTED(U_FAILURE(status))) { smart_str parse_error_str; parse_error_str = intl_parse_error_to_string(&parseError); zend_throw_exception_ex(IntlException_ce_ptr, 0, @@ -78,7 +78,7 @@ U_CFUNC PHP_METHOD(IntlRuleBasedBreakIterator, __construct) } } else { // compiled rbbi = new RuleBasedBreakIterator(reinterpret_cast(ZSTR_VAL(rules)), ZSTR_LEN(rules), status); - if (U_FAILURE(status)) { + if (UNEXPECTED(U_FAILURE(status))) { zend_throw_exception(IntlException_ce_ptr, "IntlRuleBasedBreakIterator::__construct(): unable to create instance from compiled rules", 0); delete rbbi; @@ -165,7 +165,7 @@ U_CFUNC PHP_METHOD(IntlRuleBasedBreakIterator, getBinaryRules) uint32_t rules_len; const uint8_t *rules = fetch_rbbi(bio)->getBinaryRules(rules_len); - if (rules_len > INT_MAX - 1) { + if (UNEXPECTED(rules_len > INT_MAX - 1)) { intl_errors_set(BREAKITER_ERROR_P(bio), BREAKITER_ERROR_CODE(bio), "the rules are too large"); RETURN_FALSE; diff --git a/ext/intl/common/common_date.cpp b/ext/intl/common/common_date.cpp index fb5ca6edaebe..ed18bba70b9f 100644 --- a/ext/intl/common/common_date.cpp +++ b/ext/intl/common/common_date.cpp @@ -55,7 +55,7 @@ U_CFUNC TimeZone *timezone_convert_datetimezone( minutes = offset_mins - hours * 60; minutes *= minutes > 0 ? 1 : -1; - if (offset_mins <= -24 * 60 || offset_mins >= 24 * 60) { + if (UNEXPECTED(offset_mins <= -24 * 60 || offset_mins >= 24 * 60)) { intl_errors_set(outside_error, U_ILLEGAL_ARGUMENT_ERROR, "object has an time zone offset that's too large"); return NULL; diff --git a/ext/intl/converter/converter.cpp b/ext/intl/converter/converter.cpp index b4d62d4643d1..402a50482485 100644 --- a/ext/intl/converter/converter.cpp +++ b/ext/intl/converter/converter.cpp @@ -162,7 +162,7 @@ static void php_converter_append_toUnicode_target(zval *val, UConverterToUnicode case IS_LONG: { const zend_long lval = Z_LVAL_P(val); - if ((lval < 0) || (lval > 0x10FFFF)) { + if (UNEXPECTED((lval < 0) || (lval > 0x10FFFF))) { php_converter_throw_failure(objval, U_ILLEGAL_ARGUMENT_ERROR, "Invalid codepoint U+%04lx", lval); return; } @@ -636,7 +636,7 @@ static zend_string* php_converter_do_convert(UConverter *dest_cnv, zend_string *ret; UChar *temp; - if (!src_cnv || !dest_cnv) { + if (UNEXPECTED(!src_cnv || !dest_cnv)) { php_converter_throw_failure(objval, U_INVALID_STATE_ERROR, "Internal converters not initialized"); return nullptr; @@ -686,7 +686,7 @@ static zend_string* php_converter_do_convert(UConverter *dest_cnv, static void php_converter_set_subst_chars(UConverter *cnv, const zend_string *subst, UErrorCode *error) { - if (ZSTR_LEN(subst) > SCHAR_MAX) { + if (UNEXPECTED(ZSTR_LEN(subst) > SCHAR_MAX)) { *error = U_ILLEGAL_ARGUMENT_ERROR; return; } diff --git a/ext/intl/dateformat/dateformat_format.cpp b/ext/intl/dateformat/dateformat_format.cpp index 8b2a28d5c4d2..8890332dd406 100644 --- a/ext/intl/dateformat/dateformat_format.cpp +++ b/ext/intl/dateformat/dateformat_format.cpp @@ -73,8 +73,8 @@ static int32_t internal_get_arr_ele(IntlDateFormatter_object *dfo, intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR, message); efree(message); } else { - if (Z_LVAL_P(ele_value) > INT32_MAX || - Z_LVAL_P(ele_value) < INT32_MIN) { + if (UNEXPECTED(Z_LVAL_P(ele_value) > INT32_MAX || + Z_LVAL_P(ele_value) < INT32_MIN)) { spprintf(&message, 0, "value " ZEND_LONG_FMT " is out of " "bounds for a 32-bit integer in key '%s'", Z_LVAL_P(ele_value), key_name); diff --git a/ext/intl/formatter/formatter_attr.cpp b/ext/intl/formatter/formatter_attr.cpp index 4e97ad7f7105..ff215fdffbd7 100644 --- a/ext/intl/formatter/formatter_attr.cpp +++ b/ext/intl/formatter/formatter_attr.cpp @@ -234,7 +234,7 @@ U_CFUNC PHP_FUNCTION( numfmt_get_symbol ) UNumberFormatSymbol symbol = static_cast(lsymbol); - if(symbol >= UNUM_FORMAT_SYMBOL_COUNT || symbol < 0) { + if (UNEXPECTED(symbol >= UNUM_FORMAT_SYMBOL_COUNT || symbol < 0)) { intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "invalid symbol value"); RETURN_FALSE; } @@ -276,7 +276,7 @@ U_CFUNC PHP_FUNCTION( numfmt_set_symbol ) UNumberFormatSymbol symbol = static_cast(lsymbol); - if (symbol >= UNUM_FORMAT_SYMBOL_COUNT || symbol < 0) { + if (UNEXPECTED(symbol >= UNUM_FORMAT_SYMBOL_COUNT || symbol < 0)) { intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "invalid symbol value"); RETURN_FALSE; } diff --git a/ext/intl/formatter/formatter_parse.cpp b/ext/intl/formatter/formatter_parse.cpp index 63c0fba4329f..40107229e0ae 100644 --- a/ext/intl/formatter/formatter_parse.cpp +++ b/ext/intl/formatter/formatter_parse.cpp @@ -51,7 +51,7 @@ U_CFUNC PHP_FUNCTION( numfmt_parse ) if (zposition) { zend_long long_position = zval_get_long(zposition); - if (long_position < INT32_MIN || long_position > INT32_MAX) { + if (UNEXPECTED(long_position < INT32_MIN || long_position > INT32_MAX)) { zend_argument_value_error(hasThis() ? 3 : 4, "must be between %d and %d", INT32_MIN, INT32_MAX); RETURN_THROWS(); } @@ -162,7 +162,7 @@ U_CFUNC PHP_FUNCTION( numfmt_parse_currency ) if (zposition) { zend_long long_position = zval_get_long(zposition); - if (long_position < INT32_MIN || long_position > INT32_MAX) { + if (UNEXPECTED(long_position < INT32_MIN || long_position > INT32_MAX)) { zend_argument_value_error(hasThis() ? 3 : 4, "must be between %d and %d", INT32_MIN, INT32_MAX); RETURN_THROWS(); } diff --git a/ext/intl/grapheme/grapheme_string.cpp b/ext/intl/grapheme/grapheme_string.cpp index eb8b7e609b8e..5e614be6ae72 100644 --- a/ext/intl/grapheme/grapheme_string.cpp +++ b/ext/intl/grapheme/grapheme_string.cpp @@ -104,7 +104,7 @@ U_CFUNC PHP_FUNCTION(grapheme_strpos) Z_PARAM_PATH(locale, locale_len) ZEND_PARSE_PARAMETERS_END(); - if ( OUTSIDE_STRING(loffset, haystack_len) ) { + if (UNEXPECTED(OUTSIDE_STRING(loffset, haystack_len))) { zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); } @@ -158,7 +158,7 @@ U_CFUNC PHP_FUNCTION(grapheme_stripos) Z_PARAM_PATH(locale, locale_len) ZEND_PARSE_PARAMETERS_END(); - if ( OUTSIDE_STRING(loffset, haystack_len) ) { + if (UNEXPECTED(OUTSIDE_STRING(loffset, haystack_len))) { zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); } @@ -224,7 +224,7 @@ U_CFUNC PHP_FUNCTION(grapheme_strrpos) Z_PARAM_PATH(locale, locale_len) ZEND_PARSE_PARAMETERS_END(); - if ( OUTSIDE_STRING(loffset, haystack_len) ) { + if (UNEXPECTED(OUTSIDE_STRING(loffset, haystack_len))) { zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); } @@ -283,7 +283,7 @@ U_CFUNC PHP_FUNCTION(grapheme_strripos) Z_PARAM_PATH(locale, locale_len) ZEND_PARSE_PARAMETERS_END(); - if ( OUTSIDE_STRING(loffset, haystack_len) ) { + if (UNEXPECTED(OUTSIDE_STRING(loffset, haystack_len))) { zend_argument_value_error(3, "must be contained in argument #1 ($haystack)"); RETURN_THROWS(); } @@ -358,7 +358,7 @@ U_CFUNC PHP_FUNCTION(grapheme_substr) Z_PARAM_PATH(locale, locale_len) ZEND_PARSE_PARAMETERS_END(); - if (lstart < INT32_MIN || lstart > INT32_MAX) { + if (UNEXPECTED(lstart < INT32_MIN || lstart > INT32_MAX)) { zend_argument_value_error(2, "is too large"); RETURN_THROWS(); } @@ -369,7 +369,7 @@ U_CFUNC PHP_FUNCTION(grapheme_substr) length = str_len; } - if (length < INT32_MIN || length > INT32_MAX) { + if (UNEXPECTED(length < INT32_MIN || length > INT32_MAX)) { zend_argument_value_error(3, "is too large"); RETURN_THROWS(); } @@ -758,17 +758,17 @@ U_CFUNC PHP_FUNCTION(grapheme_extract) RETURN_THROWS(); } - if ( lstart > INT32_MAX || lstart < 0 || (size_t)lstart >= str_len ) { + if (UNEXPECTED(lstart > INT32_MAX || lstart < 0 || (size_t)lstart >= str_len)) { intl_error_set( nullptr, U_ILLEGAL_ARGUMENT_ERROR, "start not contained in string"); RETURN_FALSE; } - if (size < 0) { + if (UNEXPECTED(size < 0)) { zend_argument_value_error(2, "must be greater than or equal to 0"); RETURN_THROWS(); } - if (size > INT32_MAX) { + if (UNEXPECTED(size > INT32_MAX)) { zend_argument_value_error(2, "is too large"); RETURN_THROWS(); } @@ -864,7 +864,7 @@ U_CFUNC PHP_FUNCTION(grapheme_str_split) Z_PARAM_LONG(split_len) ZEND_PARSE_PARAMETERS_END(); - if (split_len <= 0 || split_len > UINT_MAX / 4) { + if (UNEXPECTED(split_len <= 0 || split_len > UINT_MAX / 4)) { zend_argument_value_error(2, "must be greater than 0 and less than or equal to %d", UINT_MAX / 4); RETURN_THROWS(); } @@ -943,17 +943,17 @@ U_CFUNC PHP_FUNCTION(grapheme_levenshtein) Z_PARAM_PATH(locale, locale_len) ZEND_PARSE_PARAMETERS_END(); - if (cost_ins <= 0 || cost_ins > UINT_MAX / 4) { + if (UNEXPECTED(cost_ins <= 0 || cost_ins > UINT_MAX / 4)) { zend_argument_value_error(3, "must be greater than 0 and less than or equal to %d", UINT_MAX / 4); RETURN_THROWS(); } - if (cost_rep <= 0 || cost_rep > UINT_MAX / 4) { + if (UNEXPECTED(cost_rep <= 0 || cost_rep > UINT_MAX / 4)) { zend_argument_value_error(4, "must be greater than 0 and less than or equal to %d", UINT_MAX / 4); RETURN_THROWS(); } - if (cost_del <= 0 || cost_del > UINT_MAX / 4) { + if (UNEXPECTED(cost_del <= 0 || cost_del > UINT_MAX / 4)) { zend_argument_value_error(5, "must be greater than 0 and less than or equal to %d", UINT_MAX / 4); RETURN_THROWS(); } diff --git a/ext/intl/grapheme/grapheme_util.cpp b/ext/intl/grapheme/grapheme_util.cpp index 1e6fb94c9408..031f1c717891 100644 --- a/ext/intl/grapheme/grapheme_util.cpp +++ b/ext/intl/grapheme/grapheme_util.cpp @@ -59,7 +59,7 @@ U_CFUNC void grapheme_substr_ascii(char *str, size_t str_len, int32_t f, int32_t int32_t str_len2 = (int32_t)str_len; /* in order to avoid signed/unsigned problems */ *sub_str = NULL; - if(str_len > INT32_MAX) { + if (UNEXPECTED(str_len > INT32_MAX)) { /* We cannot return long strings from ICU functions, so we won't here too */ return; } diff --git a/ext/intl/idn/idn.cpp b/ext/intl/idn/idn.cpp index 28708eb93508..76cca2281296 100644 --- a/ext/intl/idn/idn.cpp +++ b/ext/intl/idn/idn.cpp @@ -40,7 +40,7 @@ enum { static zend_result php_intl_idn_check_status(UErrorCode err, const char *msg) { intl_error_set_code(NULL, err); - if (U_FAILURE(err)) { + if (UNEXPECTED(U_FAILURE(err))) { intl_error_set_custom_msg(NULL, msg); return FAILURE; } @@ -59,7 +59,7 @@ static void php_intl_idn_to_46(INTERNAL_FUNCTION_PARAMETERS, UIDNAInfo info = UIDNA_INFO_INITIALIZER; uts46 = uidna_openUTS46(option, &status); - if (php_intl_idn_check_status(status, "failed to open UIDNA instance") == FAILURE) { + if (UNEXPECTED(php_intl_idn_check_status(status, "failed to open UIDNA instance") == FAILURE)) { RETURN_FALSE; } @@ -74,7 +74,7 @@ static void php_intl_idn_to_46(INTERNAL_FUNCTION_PARAMETERS, len = uidna_nameToUnicodeUTF8(uts46, ZSTR_VAL(domain), ZSTR_LEN(domain), ZSTR_VAL(buffer), buffer_capac, &info, &status); } - if (len >= buffer_capac || php_intl_idn_check_status(status, "failed to convert name") == FAILURE) { + if (UNEXPECTED(len >= buffer_capac || php_intl_idn_check_status(status, "failed to convert name") == FAILURE)) { uidna_close(uts46); zend_string_efree(buffer); RETURN_FALSE; @@ -121,7 +121,7 @@ static void php_intl_idn_handoff(INTERNAL_FUNCTION_PARAMETERS, int mode) zend_argument_must_not_be_empty_error(1); RETURN_THROWS(); } - if (ZSTR_LEN(domain) > INT32_MAX - 1) { + if (UNEXPECTED(ZSTR_LEN(domain) > INT32_MAX - 1)) { zend_argument_value_error(1, "must be less than " PRId32 " bytes", INT32_MAX); RETURN_THROWS(); } @@ -133,7 +133,7 @@ static void php_intl_idn_handoff(INTERNAL_FUNCTION_PARAMETERS, int mode) if (idna_info != NULL) { idna_info = zend_try_array_init(idna_info); - if (!idna_info) { + if (UNEXPECTED(!idna_info)) { RETURN_THROWS(); } } diff --git a/ext/intl/intl_convert.c b/ext/intl/intl_convert.c index abbcc6c1fa73..5497e84508b4 100644 --- a/ext/intl/intl_convert.c +++ b/ext/intl/intl_convert.c @@ -59,7 +59,7 @@ void intl_convert_utf8_to_utf16( */ *status = U_ZERO_ERROR; - if(src_len > INT32_MAX) { + if (UNEXPECTED(src_len > INT32_MAX)) { /* we cannot fit this string */ *status = U_BUFFER_OVERFLOW_ERROR; return; @@ -120,7 +120,7 @@ zend_string *intl_convert_utf8_to_utf16_zstr( *status = U_ZERO_ERROR; - if(src_len > INT32_MAX) { + if (UNEXPECTED(src_len > INT32_MAX)) { /* we cannot fit this string */ *status = U_BUFFER_OVERFLOW_ERROR; return NULL; diff --git a/ext/intl/intl_data.h b/ext/intl/intl_data.h index c36f0adf5665..d2021caf18e3 100644 --- a/ext/intl/intl_data.h +++ b/ext/intl/intl_data.h @@ -114,7 +114,7 @@ typedef struct _intl_data { #define INTL_MAX_LOCALE_LEN (ULOC_FULLNAME_CAPACITY-1) #define INTL_CHECK_LOCALE_LEN(locale_len) \ - if((locale_len) > INTL_MAX_LOCALE_LEN) { \ + if (UNEXPECTED((locale_len) > INTL_MAX_LOCALE_LEN)) { \ char *_msg; \ spprintf(&_msg, 0, "Locale string too long, should be no longer than %d characters", INTL_MAX_LOCALE_LEN); \ intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, _msg); \ @@ -123,7 +123,7 @@ typedef struct _intl_data { } #define INTL_CHECK_LOCALE_LEN_OR_FAILURE(locale_len) \ - if((locale_len) > INTL_MAX_LOCALE_LEN) { \ + if (UNEXPECTED((locale_len) > INTL_MAX_LOCALE_LEN)) { \ char *_msg; \ spprintf(&_msg, 0, "Locale string too long, should be no longer than %d characters", INTL_MAX_LOCALE_LEN); \ intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, _msg); \ diff --git a/ext/intl/listformatter/listformatter_class.cpp b/ext/intl/listformatter/listformatter_class.cpp index 0f432b0e6056..59c01259227d 100644 --- a/ext/intl/listformatter/listformatter_class.cpp +++ b/ext/intl/listformatter/listformatter_class.cpp @@ -83,7 +83,7 @@ PHP_METHOD(IntlListFormatter, __construct) locale = (char *)intl_locale_get_default(); } - if (locale_len > INTL_MAX_LOCALE_LEN) { + if (UNEXPECTED(locale_len > INTL_MAX_LOCALE_LEN)) { zend_argument_value_error(1, "must be less than or equal to %d characters", INTL_MAX_LOCALE_LEN); RETURN_THROWS(); } diff --git a/ext/intl/locale/locale_methods.cpp b/ext/intl/locale/locale_methods.cpp index 0733ffc8abb7..05ec5825d8cb 100644 --- a/ext/intl/locale/locale_methods.cpp +++ b/ext/intl/locale/locale_methods.cpp @@ -353,7 +353,7 @@ static zend_string* get_icu_value_internal( const char* loc_name , const char* t int32_t buflen = 512; UErrorCode status = U_ZERO_ERROR; - if (strlen(loc_name) > INTL_MAX_LOCALE_LEN) { + if (UNEXPECTED(strlen(loc_name) > INTL_MAX_LOCALE_LEN)) { return NULL; } @@ -575,7 +575,7 @@ static void get_icu_disp_value_src_php( const char* tag_name, INTERNAL_FUNCTION_ Z_PARAM_PATH_OR_NULL(disp_loc_name, disp_loc_name_len) ZEND_PARSE_PARAMETERS_END(); - if(loc_name_len > ULOC_FULLNAME_CAPACITY) { + if (UNEXPECTED(loc_name_len > ULOC_FULLNAME_CAPACITY)) { /* See bug 67397: overlong locale names cause trouble in uloc_getDisplayName */ intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "name too long"); RETURN_FALSE; @@ -710,7 +710,7 @@ static void get_icu_disp_keyword_value_src_php(const char* tag_name, INTERNAL_FU Z_PARAM_PATH_OR_NULL(disp_loc_name, disp_loc_name_len) ZEND_PARSE_PARAMETERS_END(); - if (loc_name_len > ULOC_FULLNAME_CAPACITY) { + if (UNEXPECTED(loc_name_len > ULOC_FULLNAME_CAPACITY)) { intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "name too long"); RETURN_FALSE; } @@ -1715,7 +1715,7 @@ U_CFUNC PHP_FUNCTION(locale_accept_from_http) ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(http_accept, http_accept_len) ZEND_PARSE_PARAMETERS_END(); - if(http_accept_len > ULOC_FULLNAME_CAPACITY) { + if (UNEXPECTED(http_accept_len > ULOC_FULLNAME_CAPACITY)) { /* check each fragment, if any bigger than capacity, can't do it due to bug #72533 */ char *start = http_accept; char *end; @@ -1723,7 +1723,7 @@ U_CFUNC PHP_FUNCTION(locale_accept_from_http) do { end = strchr(start, ','); len = end ? end-start : http_accept_len-(start-http_accept); - if(len > ULOC_FULLNAME_CAPACITY) { + if (UNEXPECTED(len > ULOC_FULLNAME_CAPACITY)) { intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, "locale string too long"); RETURN_FALSE; @@ -1740,7 +1740,7 @@ U_CFUNC PHP_FUNCTION(locale_accept_from_http) &outResult, http_accept, available, &status); uenum_close(available); INTL_CHECK_STATUS(status, "failed to find acceptable locale"); - if (len < 0 || outResult == ULOC_ACCEPT_FAILED) { + if (UNEXPECTED(len < 0 || outResult == ULOC_ACCEPT_FAILED)) { RETURN_FALSE; } RETURN_STRINGL(resultLocale, len); @@ -1779,7 +1779,7 @@ U_CFUNC PHP_FUNCTION(locale_add_likely_subtags) const int32_t maximized_locale_len = uloc_addLikelySubtags(locale, maximized_locale, sizeof(maximized_locale), &status); INTL_CHECK_STATUS(status, "invalid locale"); - if (maximized_locale_len < 0) { + if (UNEXPECTED(maximized_locale_len < 0)) { RETURN_FALSE; } @@ -1802,7 +1802,7 @@ U_CFUNC PHP_FUNCTION(locale_minimize_subtags) const int32_t minimized_locale_len = uloc_minimizeSubtags(locale, minimized_locale, sizeof(minimized_locale), &status); INTL_CHECK_STATUS(status, "invalid locale"); - if (minimized_locale_len < 0) { + if (UNEXPECTED(minimized_locale_len < 0)) { RETURN_FALSE; } diff --git a/ext/intl/normalizer/normalizer_normalize.cpp b/ext/intl/normalizer/normalizer_normalize.cpp index 846eda64b4c8..8a77d37ad4d4 100644 --- a/ext/intl/normalizer/normalizer_normalize.cpp +++ b/ext/intl/normalizer/normalizer_normalize.cpp @@ -54,7 +54,7 @@ static const UNormalizer2 *intl_get_normalizer(zend_long form, UErrorCode *err) static int32_t intl_normalize(zend_long form, const UChar *src, int32_t src_len, UChar *dst, int32_t dst_len, UErrorCode *err) {/*{{{*/ const UNormalizer2 *norm = intl_get_normalizer(form, err); - if (U_FAILURE(*err)) { + if (UNEXPECTED(U_FAILURE(*err))) { return -1; } @@ -65,7 +65,7 @@ static UBool intl_is_normalized(zend_long form, const UChar *uinput, int32_t uin {/*{{{*/ const UNormalizer2 *norm = intl_get_normalizer(form, err); - if(U_FAILURE(*err)) { + if (UNEXPECTED(U_FAILURE(*err))) { return false; } @@ -151,7 +151,7 @@ U_CFUNC PHP_FUNCTION( normalizer_normalize ) * (U_BUFFER_OVERFLOW_ERROR means that *target buffer is not large enough). * (U_STRING_NOT_TERMINATED_WARNING usually means that the input string is empty). */ - if( U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR && status != U_STRING_NOT_TERMINATED_WARNING ) { + if (UNEXPECTED(U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR && status != U_STRING_NOT_TERMINATED_WARNING)) { intl_error_set_custom_msg( NULL, "Error normalizing string"); efree( uret_buf ); efree( uinput ); @@ -172,7 +172,7 @@ U_CFUNC PHP_FUNCTION( normalizer_normalize ) size_needed = intl_normalize(form, uinput, uinput_len, uret_buf, uret_len, &status); /* Bail out if an unexpected error occurred. */ - if( U_FAILURE(status) ) { + if (UNEXPECTED(U_FAILURE(status))) { /* Set error messages. */ intl_error_set_custom_msg( NULL,"Error normalizing string"); efree( uret_buf ); @@ -264,7 +264,7 @@ U_CFUNC PHP_FUNCTION( normalizer_is_normalized ) efree( uinput ); /* Bail out if an unexpected error occurred. */ - if( U_FAILURE(status) ) { + if (UNEXPECTED(U_FAILURE(status))) { /* Set error messages. */ intl_error_set_custom_msg( NULL,"Error testing if string is the given normalization form."); RETURN_FALSE; @@ -304,20 +304,20 @@ U_CFUNC PHP_FUNCTION( normalizer_get_raw_decomposition ) norm = intl_get_normalizer(form, &status); U8_NEXT(input, offset, input_length, codepoint); - if ((size_t)offset != input_length) { + if (UNEXPECTED((size_t)offset != input_length)) { intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR); intl_error_set_custom_msg(NULL, "Input string must be exactly one UTF-8 encoded code point long."); return; } - if ((codepoint < UCHAR_MIN_VALUE) || (codepoint > UCHAR_MAX_VALUE)) { + if (UNEXPECTED((codepoint < UCHAR_MIN_VALUE) || (codepoint > UCHAR_MAX_VALUE))) { intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR); intl_error_set_custom_msg(NULL, "Code point out of range"); return; } decomposition_length = unorm2_getRawDecomposition(norm, codepoint, decomposition, 32, &status); - if (decomposition_length == -1) { + if (UNEXPECTED(decomposition_length == -1)) { RETURN_NULL(); } diff --git a/ext/intl/php_intl.c b/ext/intl/php_intl.c index 9882309c6f46..697d77a98bfa 100644 --- a/ext/intl/php_intl.c +++ b/ext/intl/php_intl.c @@ -106,7 +106,7 @@ char* canonicalize_locale_string(const char* locale) { canonicalized_len = uloc_canonicalize(locale, canonicalized, sizeof(canonicalized), &status); - if (U_FAILURE(status) || canonicalized_len <= 0) { + if (UNEXPECTED(U_FAILURE(status) || canonicalized_len <= 0)) { return NULL; } diff --git a/ext/intl/rangeformatter/rangeformatter_class.cpp b/ext/intl/rangeformatter/rangeformatter_class.cpp index f933e0c06090..2dbb60c5b639 100644 --- a/ext/intl/rangeformatter/rangeformatter_class.cpp +++ b/ext/intl/rangeformatter/rangeformatter_class.cpp @@ -99,7 +99,7 @@ U_CFUNC PHP_METHOD(IntlNumberRangeFormatter, createFromSkeleton) locale = (char *)intl_locale_get_default(); } - if (locale_len > INTL_MAX_LOCALE_LEN) { + if (UNEXPECTED(locale_len > INTL_MAX_LOCALE_LEN)) { zend_argument_value_error(2, "must be no longer than %d characters", INTL_MAX_LOCALE_LEN); RETURN_THROWS(); } diff --git a/ext/intl/resourcebundle/resourcebundle_class.cpp b/ext/intl/resourcebundle/resourcebundle_class.cpp index 7e22e9e8c7df..a997691f6423 100644 --- a/ext/intl/resourcebundle/resourcebundle_class.cpp +++ b/ext/intl/resourcebundle/resourcebundle_class.cpp @@ -108,7 +108,7 @@ static zend_result resourcebundle_ctor(INTERNAL_FUNCTION_PARAMETERS) locale = (char *)intl_locale_get_default(); } - if (bundlename_len >= MAXPATHLEN) { + if (UNEXPECTED(bundlename_len >= MAXPATHLEN)) { zend_argument_value_error(2, "is too long"); return FAILURE; } @@ -349,7 +349,7 @@ U_CFUNC PHP_FUNCTION( resourcebundle_locales ) Z_PARAM_STRING(bundlename, bundlename_len) ZEND_PARSE_PARAMETERS_END(); - if (bundlename_len >= MAXPATHLEN) { + if (UNEXPECTED(bundlename_len >= MAXPATHLEN)) { zend_argument_value_error(1, "is too long"); RETURN_THROWS(); } diff --git a/ext/intl/spoofchecker/spoofchecker_main.cpp b/ext/intl/spoofchecker/spoofchecker_main.cpp index cfd5ad8fb631..1e8e9ac2a78d 100644 --- a/ext/intl/spoofchecker/spoofchecker_main.cpp +++ b/ext/intl/spoofchecker/spoofchecker_main.cpp @@ -77,7 +77,7 @@ U_CFUNC PHP_METHOD(Spoofchecker, areConfusable) ZEND_PARSE_PARAMETERS_END(); SPOOFCHECKER_METHOD_FETCH_OBJECT; - if(ZSTR_LEN(s1) > INT32_MAX || ZSTR_LEN(s2) > INT32_MAX) { + if (UNEXPECTED(ZSTR_LEN(s1) > INT32_MAX || ZSTR_LEN(s2) > INT32_MAX)) { SPOOFCHECKER_ERROR_CODE(co) = U_BUFFER_OVERFLOW_ERROR; } else { ret = uspoof_areConfusableUTF8(co->uspoof, ZSTR_VAL(s1), (int32_t)ZSTR_LEN(s1), ZSTR_VAL(s2), (int32_t)ZSTR_LEN(s2), SPOOFCHECKER_ERROR_CODE_P(co)); @@ -179,7 +179,7 @@ U_CFUNC PHP_METHOD(Spoofchecker, setAllowedChars) ZEND_PARSE_PARAMETERS_END(); SPOOFCHECKER_METHOD_FETCH_OBJECT; - if (ZSTR_LEN(pattern) > INT32_MAX) { + if (UNEXPECTED(ZSTR_LEN(pattern) > INT32_MAX)) { zend_argument_value_error(1, "must be less than or equal to " ZEND_LONG_FMT " bytes long", INT32_MAX); RETURN_THROWS(); } diff --git a/ext/intl/transliterator/transliterator_methods.cpp b/ext/intl/transliterator/transliterator_methods.cpp index 8efcff95b310..9cdec2611a24 100644 --- a/ext/intl/transliterator/transliterator_methods.cpp +++ b/ext/intl/transliterator/transliterator_methods.cpp @@ -331,17 +331,17 @@ U_CFUNC PHP_FUNCTION( transliterator_transliterate ) RETURN_THROWS(); } - if (limit < -1) { + if (UNEXPECTED(limit < -1)) { zend_argument_value_error(is_method ? 3 : 4, "must be greater than or equal to -1"); goto cleanup_object; } - if (start < 0) { + if (UNEXPECTED(start < 0)) { zend_argument_value_error(is_method ? 2 : 3, "must be greater than or equal to 0"); goto cleanup_object; } - if (limit != -1 && start > limit) { + if (UNEXPECTED(limit != -1 && start > limit)) { zend_argument_value_error(is_method ? 2 : 3, "must be less than or equal to argument #%d ($end)", is_method ? 3 : 4); goto cleanup_object; } @@ -355,7 +355,7 @@ U_CFUNC PHP_FUNCTION( transliterator_transliterate ) /* we've started allocating resources, goto from now on */ - if( ( start > ustr_len ) || (( limit != -1 ) && (limit > ustr_len ) ) ) + if (UNEXPECTED((start > ustr_len) || ((limit != -1) && (limit > ustr_len)))) { char *msg; spprintf( &msg, 0, diff --git a/ext/intl/uchar/uchar.cpp b/ext/intl/uchar/uchar.cpp index 8dc6a58a8287..80bdd2bdb9fa 100644 --- a/ext/intl/uchar/uchar.cpp +++ b/ext/intl/uchar/uchar.cpp @@ -30,21 +30,21 @@ static inline int convert_cp(UChar32* pcp, const zend_string *string_codepoint, int32_t i = 0; const size_t string_codepoint_length = ZSTR_LEN(string_codepoint); - if (ZEND_SIZE_T_INT_OVFL(string_codepoint_length)) { + if (UNEXPECTED(ZEND_SIZE_T_INT_OVFL(string_codepoint_length))) { intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR); intl_error_set_custom_msg(NULL, "Input string is too long."); return FAILURE; } U8_NEXT(ZSTR_VAL(string_codepoint), i, string_codepoint_length, int_codepoint); - if ((size_t)i != string_codepoint_length) { + if (UNEXPECTED((size_t)i != string_codepoint_length)) { intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR); intl_error_set_custom_msg(NULL, "Passing a UTF-8 character for codepoint requires a string which is exactly one UTF-8 codepoint long."); return FAILURE; } } - if ((int_codepoint < UCHAR_MIN_VALUE) || (int_codepoint > UCHAR_MAX_VALUE)) { + if (UNEXPECTED((int_codepoint < UCHAR_MIN_VALUE) || (int_codepoint > UCHAR_MAX_VALUE))) { intl_error_set_code(NULL, U_ILLEGAL_ARGUMENT_ERROR); intl_error_set_custom_msg(NULL, "Codepoint out of range"); return FAILURE; diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c index 5df4d47740a8..9bc801455ed6 100644 --- a/ext/zip/php_zip.c +++ b/ext/zip/php_zip.c @@ -628,6 +628,12 @@ static bool php_zipobj_close(ze_zip_object *obj, zend_string **out_str) /* {{{ * obj->za = NULL; obj->from_string = false; + + if (obj->bailout_callback) { + obj->bailout_callback = false; + zend_bailout(); + } + return success; } /* }}} */ @@ -1073,10 +1079,16 @@ static void php_zip_object_dtor(zend_object *object) if (intern->za) { if (zip_close(intern->za) != 0) { - php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(intern->za)); + if (!intern->bailout_callback) { + php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(intern->za)); + } zip_discard(intern->za); } intern->za = NULL; + if (intern->bailout_callback) { + intern->bailout_callback = false; + zend_bailout(); + } } } @@ -2985,15 +2997,21 @@ PHP_METHOD(ZipArchive, getStream) #ifdef HAVE_PROGRESS_CALLBACK static void php_zip_progress_callback(zip_t *arch, double state, void *ptr) { - if (!EG(active)) { + ze_zip_object *obj = ptr; + + if (UNEXPECTED(!EG(active) || obj->bailout_callback)) { return; } zval cb_args[1]; - ze_zip_object *obj = ptr; ZVAL_DOUBLE(&cb_args[0], state); - zend_call_known_fcc(&obj->progress_callback, NULL, 1, cb_args, NULL); + + zend_try { + zend_call_known_fcc(&obj->progress_callback, NULL, 1, cb_args, NULL); + } zend_catch { + obj->bailout_callback = true; + } zend_end_try(); } /* {{{ register a progression callback: void callback(double state); */ @@ -3036,11 +3054,18 @@ static int php_zip_cancel_callback(zip_t *arch, void *ptr) zval cb_retval; ze_zip_object *obj = ptr; - if (!EG(active)) { + if (UNEXPECTED(!EG(active) || obj->bailout_callback)) { return 0; } - zend_call_known_fcc(&obj->cancel_callback, &cb_retval, 0, NULL, NULL); + zend_try { + zend_call_known_fcc(&obj->cancel_callback, &cb_retval, 0, NULL, NULL); + } zend_catch { + obj->bailout_callback = true; + /* Cancel if a bailout occurs to allow cleanup to happen */ + return -1; + } zend_end_try(); + if (Z_ISUNDEF(cb_retval)) { /* Cancel if an exception has been thrown */ return -1; diff --git a/ext/zip/php_zip.h b/ext/zip/php_zip.h index 9e57a54de1cc..408c29708231 100644 --- a/ext/zip/php_zip.h +++ b/ext/zip/php_zip.h @@ -74,6 +74,7 @@ typedef struct _ze_zip_object { zip_int64_t last_id; int err_zip; int err_sys; + bool bailout_callback; #ifdef HAVE_PROGRESS_CALLBACK zend_fcall_info_cache progress_callback; #endif diff --git a/ext/zip/tests/gh22176.phpt b/ext/zip/tests/gh22176.phpt new file mode 100644 index 000000000000..90b6327072c9 --- /dev/null +++ b/ext/zip/tests/gh22176.phpt @@ -0,0 +1,33 @@ +--TEST-- +GH-22176 (Memory leak when a ZipArchive cancel callback bails out in the shutdown destructor) +--EXTENSIONS-- +zip +--SKIPIF-- + +--FILE-- +open(__DIR__ . '/gh22176_cancel.zip', ZipArchive::CREATE); +$zip->registerCancelCallback(function () { + throw new \Exception('cancel boom'); +}); +$zip->addFromString('test', 'test'); +echo "done\n"; +// The archive is flushed and the object destroyed during request shutdown; +// the thrown exception bails out through libzip's zip_close() without leaking +// its internal state, and the bailout resumes once libzip has unwound. +?> +--CLEAN-- + +--EXPECTF-- +done + +Fatal error: Uncaught Exception: cancel boom in %s:%d +Stack trace: +#0 [internal function]: {closure:%s:%d}() +#1 {main} + thrown in %s on line %d diff --git a/ext/zip/tests/gh22176_progress.phpt b/ext/zip/tests/gh22176_progress.phpt new file mode 100644 index 000000000000..5507fe24ab98 --- /dev/null +++ b/ext/zip/tests/gh22176_progress.phpt @@ -0,0 +1,33 @@ +--TEST-- +GH-22176 (Memory leak when a ZipArchive progress callback bails out in the shutdown destructor) +--EXTENSIONS-- +zip +--SKIPIF-- + +--FILE-- +open(__DIR__ . '/gh22176_progress.zip', ZipArchive::CREATE); +$zip->registerProgressCallback(0.5, function ($r) { + throw new \Exception('progress boom'); +}); +$zip->addFromString('test', 'test'); +echo "done\n"; +// The archive is flushed and the object destroyed during request shutdown; +// the thrown exception bails out through libzip's zip_close() without leaking +// its internal state, and the bailout resumes once libzip has unwound. +?> +--CLEAN-- + +--EXPECTF-- +done + +Fatal error: Uncaught Exception: progress boom in %s:%d +Stack trace: +#0 [internal function]: {closure:%s:%d}(0.0) +#1 {main} + thrown in %s on line %d