diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index e16a7bdae805..45c4ef34e852 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -40,6 +40,8 @@ PHP 8.7 INTERNALS UPGRADE NOTES - Added a new zend_object_copy() function which increases the refcount of a zend_object* and returns it. Allowing more descriptive code rather than doing an assignment followed by GC_ADDREF(). +- Added zend_call_method_if_exists_ex() which accepts named parameters and + retrieving the callability failure error message. ======================== 2. Build system changes diff --git a/Zend/zend_API.h b/Zend/zend_API.h index c94a71cf56b8..425139e3a00b 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -913,12 +913,21 @@ static zend_always_inline void zend_call_known_instance_method_with_1_params( ZEND_API void zend_call_known_instance_method_with_2_params( zend_function *fn, zend_object *this_ptr, zval *retval_ptr, zval *param1, zval *param2); -/* Call method if it exists. Return FAILURE if method does not exist or call failed. - * If FAILURE is returned, retval will be UNDEF. As such, destroying retval unconditionally - * is legal. */ -ZEND_API zend_result zend_call_method_if_exists( - zend_object *this_ptr, zend_string *method_name, zval *retval, - uint32_t param_count, zval *params); +/* Call method if it exists. + * Return FAILURE if method does not exist. + * If FAILURE is returned, retval will be UNDEF, and error will contain the validation failure message if provided. + * As such, destroying retval unconditionally is legal. + */ +ZEND_API zend_result zend_call_method_if_exists_ex( + zend_object *this_ptr, zend_string *method_name, zval *retval_ptr, + uint32_t param_count, zval *params, HashTable *named_params, char **error); + +static zend_always_inline zend_result zend_call_method_if_exists( + zend_object *this_ptr, zend_string *method_name, + zval *retval_ptr, uint32_t param_count, zval *params +) { + return zend_call_method_if_exists_ex(this_ptr, method_name, retval_ptr, param_count, params, NULL, NULL); +} ZEND_API zend_result zend_delete_global_variable(zend_string *name); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index a1b7c16ba73f..beb3f9f995af 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1184,21 +1184,21 @@ ZEND_API void zend_call_known_instance_method_with_2_params( zend_call_known_instance_method(fn, this_ptr, retval_ptr, 2, params); } -ZEND_API zend_result zend_call_method_if_exists( - zend_object *this_ptr, zend_string *method_name, zval *retval, - uint32_t param_count, zval *params) -{ +ZEND_API zend_result zend_call_method_if_exists_ex( + zend_object *this_ptr, zend_string *method_name, zval *retval_ptr, + uint32_t param_count, zval *params, HashTable *named_params, char **error +) { zval zval_method; zend_fcall_info_cache fcc; ZVAL_STR(&zval_method, method_name); - if (UNEXPECTED(!zend_is_callable_ex(&zval_method, this_ptr, IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, &fcc, NULL))) { - ZVAL_UNDEF(retval); + if (UNEXPECTED(!zend_is_callable_ex(&zval_method, this_ptr, IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, &fcc, error))) { + ZVAL_UNDEF(retval_ptr); return FAILURE; } - zend_call_known_fcc(&fcc, retval, param_count, params, NULL); + zend_call_known_fcc(&fcc, retval_ptr, param_count, params, named_params); /* Need to free potential trampoline (__call/__callStatic) copied function handler before releasing the closure */ zend_release_fcall_info_cache(&fcc); return SUCCESS; diff --git a/ext/soap/soap.c b/ext/soap/soap.c index a96a21162c20..7266b06f8294 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -1100,7 +1100,9 @@ PHP_METHOD(SoapServer, __construct) service->version = version; service->type = SOAP_FUNCTIONS; service->soap_functions.functions_all = false; - service->soap_functions.ft = zend_new_array(0); + ALLOC_HASHTABLE(service->soap_functions.ft); + /* This hashtable contains zend_function pointers so doesn't need a destructor */ + zend_hash_init(service->soap_functions.ft, 0, NULL, NULL, false); SOAP_SERVER_BEGIN_CODE(); if (wsdl) { @@ -1226,7 +1228,7 @@ PHP_METHOD(SoapServer, setObject) PHP_METHOD(SoapServer, getFunctions) { soapServicePtr service; - HashTable *ft = NULL; + const HashTable *ft = NULL; ZEND_PARSE_PARAMETERS_NONE(); @@ -1240,11 +1242,7 @@ PHP_METHOD(SoapServer, getFunctions) } else if (service->soap_functions.functions_all) { ft = EG(function_table); } else if (service->soap_functions.ft != NULL) { - zval *name; - - ZEND_HASH_MAP_FOREACH_VAL(service->soap_functions.ft, name) { - add_next_index_str(return_value, zend_string_copy(Z_STR_P(name))); - } ZEND_HASH_FOREACH_END(); + ft = service->soap_functions.ft; } if (ft != NULL) { zend_function *f; @@ -1263,7 +1261,7 @@ PHP_METHOD(SoapServer, getFunctions) PHP_METHOD(SoapServer, addFunction) { soapServicePtr service; - zval *function_name, function_copy; + zval *function_name; if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &function_name) == FAILURE) { RETURN_THROWS(); @@ -1279,7 +1277,9 @@ PHP_METHOD(SoapServer, addFunction) if (service->soap_functions.ft == NULL) { service->soap_functions.functions_all = false; - service->soap_functions.ft = zend_new_array(zend_hash_num_elements(Z_ARRVAL_P(function_name))); + ALLOC_HASHTABLE(service->soap_functions.ft); + /* This hashtable contains zend_function pointers so doesn't need a destructor */ + zend_hash_init(service->soap_functions.ft, zend_hash_num_elements(Z_ARRVAL_P(function_name)), NULL, NULL, false); } ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(function_name), tmp_function) { @@ -1292,15 +1292,15 @@ PHP_METHOD(SoapServer, addFunction) } key = zend_string_tolower(Z_STR_P(tmp_function)); + f = zend_hash_find_ptr(EG(function_table), key); - if ((f = zend_hash_find_ptr(EG(function_table), key)) == NULL) { + if (f == NULL) { zend_string_release_ex(key, false); zend_type_error("SoapServer::addFunction(): Function \"%s\" not found", Z_STRVAL_P(tmp_function)); RETURN_THROWS(); } - ZVAL_STR_COPY(&function_copy, f->common.function_name); - zend_hash_update(service->soap_functions.ft, key, &function_copy); + zend_hash_update_ptr(service->soap_functions.ft, key, f); zend_string_release_ex(key, 0); } ZEND_HASH_FOREACH_END(); @@ -1310,19 +1310,20 @@ PHP_METHOD(SoapServer, addFunction) zend_function *f; key = zend_string_tolower(Z_STR_P(function_name)); - - if ((f = zend_hash_find_ptr(EG(function_table), key)) == NULL) { + f = zend_hash_find_ptr(EG(function_table), key); + if (f == NULL) { zend_string_release_ex(key, false); zend_argument_type_error(1, "must be a valid function name, function \"%s\" not found", Z_STRVAL_P(function_name)); RETURN_THROWS(); } if (service->soap_functions.ft == NULL) { service->soap_functions.functions_all = false; - service->soap_functions.ft = zend_new_array(0); + ALLOC_HASHTABLE(service->soap_functions.ft); + /* This hashtable contains zend_function pointers so doesn't need a destructor */ + zend_hash_init(service->soap_functions.ft, 0, NULL, NULL, false); } - ZVAL_STR_COPY(&function_copy, f->common.function_name); - zend_hash_update(service->soap_functions.ft, key, &function_copy); + zend_hash_update_ptr(service->soap_functions.ft, key, f); zend_string_release_ex(key, 0); } else if (Z_TYPE_P(function_name) == IS_LONG) { if (Z_LVAL_P(function_name) == SOAP_FUNCTIONS_ALL) { @@ -1367,6 +1368,14 @@ static void _soap_server_exception(soapServicePtr service, sdlFunctionPtr functi } /* }}} */ +static void soap_free_server_object(soapServicePtr service, zval *soap_object) +{ + if (service->type == SOAP_CLASS && soap_object && service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { + zval_ptr_dtor(soap_object); + ZVAL_UNDEF(soap_object); + } +} + /* {{{ Handles a SOAP request */ PHP_METHOD(SoapServer, handle) { @@ -1374,12 +1383,11 @@ PHP_METHOD(SoapServer, handle) sdlPtr old_sdl = NULL; soapServicePtr service; xmlDocPtr doc_request = NULL, doc_return = NULL; - zval function_name, *params, *soap_obj, retval; + zval function_name, *params, retval; char cont_len[30]; uint32_t num_params = 0; - int size, i, call_status = 0; + int size, i; xmlChar *buf; - HashTable *function_table; soapHeader *soap_headers = NULL; sdlFunctionPtr function; char *arg = NULL; @@ -1554,10 +1562,10 @@ PHP_METHOD(SoapServer, handle) service->soap_headers_ptr = &soap_headers; - soap_obj = NULL; + zval *soap_obj = NULL; + HashTable *function_table = NULL; if (service->type == SOAP_OBJECT) { soap_obj = &service->soap_object; - function_table = &((Z_OBJCE_P(soap_obj))->function_table); } else if (service->type == SOAP_CLASS) { /* If persistent then set soap_obj from the previous created session (if available) */ #ifdef SOAP_HAS_SESSION_SUPPORT @@ -1581,7 +1589,7 @@ PHP_METHOD(SoapServer, handle) } #endif - /* If new session or something weird happned */ + /* If new session or something weird happened */ if (soap_obj == NULL) { if (UNEXPECTED(object_init_ex(&tmp_soap, service->soap_class.ce) != SUCCESS)) { php_output_discard(); @@ -1619,7 +1627,6 @@ PHP_METHOD(SoapServer, handle) soap_obj = &tmp_soap; } } - function_table = &((Z_OBJCE_P(soap_obj))->function_table); } else { if (service->soap_functions.functions_all) { function_table = EG(function_table); @@ -1644,103 +1651,125 @@ PHP_METHOD(SoapServer, handle) } } #endif - if (zend_hash_find_ptr_lc(function_table, Z_STR(h->function_name)) != NULL || - ((service->type == SOAP_CLASS || service->type == SOAP_OBJECT) && - zend_hash_str_exists(function_table, ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME)-1))) { - call_status = call_user_function(NULL, soap_obj, &h->function_name, &h->retval, h->num_params, h->parameters); - if (call_status != SUCCESS) { - php_error_docref(NULL, E_WARNING, "Function '%s' call failed", Z_STRVAL(h->function_name)); - return; - } - if (Z_TYPE(h->retval) == IS_OBJECT && - instanceof_function(Z_OBJCE(h->retval), soap_fault_class_entry)) { - php_output_discard(); - soap_server_fault_ex(function, &h->retval, h); - if (service->type == SOAP_CLASS && soap_obj) { - if (service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { - zval_ptr_dtor(soap_obj); - } + if (soap_obj) { + /* This is because the object might define a __call() magic method */ + zend_result method_call_result = zend_call_method_if_exists( + Z_OBJ_P(soap_obj), + Z_STR(h->function_name), + &h->retval, + h->num_params, + h->parameters + ); + if (UNEXPECTED(method_call_result == FAILURE)) { + if (h->mustUnderstand) { + soap_server_fault_en("MustUnderstand","Header not understood"); + goto fail; } - goto fail; - } else if (EG(exception)) { - php_output_discard(); - _soap_server_exception(service, function, ZEND_THIS); - if (service->type == SOAP_CLASS && soap_obj) { - if (service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { - zval_ptr_dtor(soap_obj); - } + ZVAL_NULL(&h->retval); + continue; + } + } else { + zend_function *header_fn = zend_hash_find_ptr_lc(function_table, Z_STR(h->function_name)); + if (UNEXPECTED(header_fn == NULL)) { + if (h->mustUnderstand) { + soap_server_fault_en("MustUnderstand","Header not understood"); + goto fail; } - goto fail; + ZVAL_NULL(&h->retval); + continue; } - } else if (h->mustUnderstand) { - soap_server_fault_en("MustUnderstand","Header not understood"); + zend_call_known_function(header_fn, NULL, NULL, &h->retval, h->num_params, h->parameters, NULL); + } + + if (Z_TYPE(h->retval) == IS_OBJECT && + instanceof_function(Z_OBJCE(h->retval), soap_fault_class_entry)) { + php_output_discard(); + soap_server_fault_ex(function, &h->retval, h); + soap_free_server_object(service, soap_obj); + goto fail; + } else if (EG(exception)) { + php_output_discard(); + _soap_server_exception(service, function, ZEND_THIS); + soap_free_server_object(service, soap_obj); + goto fail; } } } - if (zend_hash_find_ptr_lc(function_table, Z_STR(function_name)) != NULL || - ((service->type == SOAP_CLASS || service->type == SOAP_OBJECT) && - zend_hash_str_exists(function_table, ZEND_CALL_FUNC_NAME, sizeof(ZEND_CALL_FUNC_NAME)-1))) { - call_status = call_user_function(NULL, soap_obj, &function_name, &retval, num_params, params); - if (service->type == SOAP_CLASS) { - if (service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { - zval_ptr_dtor(soap_obj); - soap_obj = NULL; - } + if (soap_obj) { + char *error = NULL; + /* This is because the object might define a __call() magic method */ + zend_result method_call_result = zend_call_method_if_exists_ex( + Z_OBJ_P(soap_obj), + Z_STR(function_name), + &retval, + num_params, + params, + NULL, + &error + ); + if (UNEXPECTED(method_call_result == FAILURE)) { + ZEND_ASSERT(error != NULL); + zend_throw_error(NULL, "Cannot call method %s::%s(): %s", + ZSTR_VAL(Z_OBJCE_P(soap_obj)->name), + Z_STRVAL(function_name), + error + ); + php_output_discard(); + _soap_server_exception(service, function, ZEND_THIS); + soap_free_server_object(service, soap_obj); + goto fail; } } else { - php_error(E_ERROR, "Function '%s' doesn't exist", Z_STRVAL(function_name)); + zend_function *fn = zend_hash_find_ptr_lc(function_table, Z_STR(function_name)); + if (UNEXPECTED(fn == NULL)) { + zend_throw_error(NULL, "Call to undefined function %s()", Z_STRVAL(function_name)); + php_output_discard(); + _soap_server_exception(service, function, ZEND_THIS); + soap_free_server_object(service, soap_obj); + goto fail; + } + zend_call_known_function(fn, NULL, NULL, &retval, num_params, params, NULL); } + soap_free_server_object(service, soap_obj); + if (EG(exception)) { if (!zend_is_unwind_exit(EG(exception))) { php_output_discard(); _soap_server_exception(service, function, ZEND_THIS); - if (service->type == SOAP_CLASS) { - if (soap_obj && service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { - zval_ptr_dtor(soap_obj); - } - } + soap_free_server_object(service, soap_obj); } goto fail; } - if (call_status == SUCCESS) { - char *response_name; - - if (Z_TYPE(retval) == IS_OBJECT && - instanceof_function(Z_OBJCE(retval), soap_fault_class_entry)) { - php_output_discard(); - soap_server_fault_ex(function, &retval, NULL); - goto fail; - } + char *response_name; - bool has_response_name = function && function->responseName; - if (has_response_name) { - response_name = function->responseName; - } else { - response_name = emalloc(Z_STRLEN(function_name) + sizeof("Response")); - memcpy(response_name,Z_STRVAL(function_name),Z_STRLEN(function_name)); - memcpy(response_name+Z_STRLEN(function_name),"Response",sizeof("Response")); - } - doc_return = serialize_response_call(function, response_name, service->uri, &retval, soap_headers, soap_version); + if (Z_TYPE(retval) == IS_OBJECT && + instanceof_function(Z_OBJCE(retval), soap_fault_class_entry)) { + php_output_discard(); + soap_server_fault_ex(function, &retval, NULL); + goto fail; + } - if (!has_response_name) { - efree(response_name); - } + bool has_response_name = function && function->responseName; + if (has_response_name) { + response_name = function->responseName; } else { - php_error_docref(NULL, E_WARNING, "Function '%s' call failed", Z_STRVAL(function_name)); - return; + response_name = emalloc(Z_STRLEN(function_name) + sizeof("Response")); + memcpy(response_name,Z_STRVAL(function_name),Z_STRLEN(function_name)); + memcpy(response_name+Z_STRLEN(function_name),"Response",sizeof("Response")); + } + doc_return = serialize_response_call(function, response_name, service->uri, &retval, soap_headers, soap_version); + + if (!has_response_name) { + efree(response_name); } if (EG(exception)) { php_output_discard(); _soap_server_exception(service, function, ZEND_THIS); - if (service->type == SOAP_CLASS) { - if (soap_obj && service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { - zval_ptr_dtor(soap_obj); - } - } + soap_free_server_object(service, soap_obj); goto fail; } diff --git a/ext/soap/tests/SoapServer/persistent-session-no-free-SoapFault.phpt b/ext/soap/tests/SoapServer/persistent-session-no-free-SoapFault.phpt new file mode 100644 index 000000000000..02123b0b4682 --- /dev/null +++ b/ext/soap/tests/SoapServer/persistent-session-no-free-SoapFault.phpt @@ -0,0 +1,65 @@ +--TEST-- +SoapServer::handle() must not free the session persisted object when a header handler returns a SoapFault +--EXTENSIONS-- +soap +session +--INI-- +session.save_handler=files +session.use_cookies=0 +session.use_strict_mode=0 +soap.wsdl_cache_enabled=0 +--FILE-- + 'http://test-uri']); +$server->setClass('Handler'); +$server->setPersistence(SOAP_PERSISTENCE_SESSION); + +$request = << + + 1 + + +XML; + +$server->handle($request); + +echo 'after handle', PHP_EOL; +var_dump($_SESSION['_bogus_session_name']->property); +echo 'end', PHP_EOL; +?> +--CLEAN-- + +--EXPECT-- + +SOAP-ENV:Serverheader fault +after handle +string(4) "kept" +end +Handler::__destruct diff --git a/ext/soap/tests/bugs/bug73037.phpt b/ext/soap/tests/bugs/bug73037.phpt index 6d94fca04e0f..4ef8a7a25e66 100644 --- a/ext/soap/tests/bugs/bug73037.phpt +++ b/ext/soap/tests/bugs/bug73037.phpt @@ -136,42 +136,42 @@ cleanup: --EXPECT-- Iteration 0 -Function 'CATALOG' doesn't exist +Cannot call method stdClass::CATALOG(): class stdClass does not have a method "CATALOG" -Function 'CATALOG' doesn't exist +Cannot call method stdClass::CATALOG(): class stdClass does not have a method "CATALOG" Iteration 1 -Function 'CATALOG' doesn't exist +Cannot call method stdClass::CATALOG(): class stdClass does not have a method "CATALOG" -Function 'CATALOG' doesn't exist +Cannot call method stdClass::CATALOG(): class stdClass does not have a method "CATALOG" Iteration 2 -Function 'CATALOG' doesn't exist +Cannot call method stdClass::CATALOG(): class stdClass does not have a method "CATALOG" -Function 'CATALOG' doesn't exist +Cannot call method stdClass::CATALOG(): class stdClass does not have a method "CATALOG" Iteration 3 -Function 'CATALOG' doesn't exist +Cannot call method stdClass::CATALOG(): class stdClass does not have a method "CATALOG" -Function 'CATALOG' doesn't exist +Cannot call method stdClass::CATALOG(): class stdClass does not have a method "CATALOG" Iteration 4 -Function 'CATALOG' doesn't exist +Cannot call method stdClass::CATALOG(): class stdClass does not have a method "CATALOG" -Function 'CATALOG' doesn't exist +Cannot call method stdClass::CATALOG(): class stdClass does not have a method "CATALOG" Iteration 5 -Function 'CATALOG' doesn't exist +Cannot call method stdClass::CATALOG(): class stdClass does not have a method "CATALOG" -Function 'CATALOG' doesn't exist +Cannot call method stdClass::CATALOG(): class stdClass does not have a method "CATALOG" Iteration 6 -Function 'CATALOG' doesn't exist +Cannot call method stdClass::CATALOG(): class stdClass does not have a method "CATALOG" -Function 'CATALOG' doesn't exist +Cannot call method stdClass::CATALOG(): class stdClass does not have a method "CATALOG" diff --git a/ext/soap/tests/gh22218.phpt b/ext/soap/tests/gh22218.phpt index 5b0714037a0a..acbaf685e381 100644 --- a/ext/soap/tests/gh22218.phpt +++ b/ext/soap/tests/gh22218.phpt @@ -21,5 +21,6 @@ $_SERVER = 79; $server = new SoapServer(null, ['uri' => 'http://test-uri']); $server->handle(); ?> ---EXPECTF-- -%AFunction 'test' doesn't exist%A +--EXPECT-- + +SOAP-ENV:ServerCall to undefined function test()