Skip to content

ext/soap: Refactor userland function calling code - #19055

Merged
Girgias merged 5 commits into
php:masterfrom
Girgias:soap-call-func-directly
Oct 2, 2026
Merged

Girgias merged 5 commits into
php:masterfrom
Girgias:soap-call-func-directly

Conversation

@Girgias

@Girgias Girgias commented Jul 6, 2025

Copy link
Copy Markdown
Member

Commits should be reviewed in order.

This gets rid of some more call_user_function API calls and reduces the amount of string copying.

@Girgias
Girgias marked this pull request as ready for review July 6, 2025 17:48
@Girgias
Girgias requested a review from ndossche as a code owner July 6, 2025 17:48
Comment thread ext/soap/soap.c Outdated
Comment thread ext/soap/soap.c Outdated
Comment thread ext/soap/soap.c Outdated
Comment thread ext/soap/soap.c Outdated
zend_function *header_fn = zend_hash_find_ptr_lc(function_table, Z_STR(h->function_name));
if (UNEXPECTED(header_fn == NULL)) {
if (soap_obj_ce && soap_obj_ce->__call) {
header_fn = zend_get_call_trampoline_func(soap_obj_ce, Z_STR(function_name), false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using zend_get_call_trampoline_func you should also release the trampoline function when you're done with it. This usually doesn't cause problems because as long as you're outside of a trampoline call, this function will store the trampoline in EG(trampoline). It should be possible to test this by making a soap call from within a trampoline.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must be honest that I'm not exactly certain how to write a test for SOAP, do I need to "call" the trampoline from the XML?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll instantiate a SoapServer class and use setObject with a class that has a __call implementation.
The SoapServer::handle() function should be called from within another class's __call implementation.
That way, there are 2 trampolines active, and so the one invoked in this C code will use a heap-allocated function structure.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, I'm starting to wonder whether the manual trampoline handling will make the code more complex than it already was

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm working on something more long term to see if I can get rid of the function_name zval of the FCI so manual trampoline handling is something that is a bit annoying, I agree.

Not sure if there should be a new API with a "backing" zval.

@Girgias
Girgias force-pushed the soap-call-func-directly branch from f55a976 to 66d6c68 Compare July 12, 2025 19:34
Comment thread ext/soap/soap.c
Comment on lines 1218 to 1223
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);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This snippet is repeated 2 times, and the allocation with the comment 3 times. I wonder if this can be split off to a helper function (e.g. "soap_ensure_functions_table" or something alike). Could be left for a follow-up

Comment thread ext/soap/soap.c
return;

if (soap_obj) {
/* This is because the object might define a __call() magic method */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need a better explanation than "This is", what is "this" ;)

Comment thread ext/soap/soap.c
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)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if you can factor out the "mustunderstand" stuff

@devnexen

Copy link
Copy Markdown
Member

definitely not ready to merge, couple of remarks.

Comment thread ext/soap/soap.c
Comment thread ext/soap/soap.c
@Girgias
Girgias force-pushed the soap-call-func-directly branch from 3a3ce33 to 4f51fe0 Compare October 2, 2026 12:58
Comment thread ext/soap/soap.c Outdated
soap_obj = NULL;
}
}
soap_free_server_object(service, soap_obj);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused here why we don't discard the php output?

Comment thread ext/soap/soap.c Outdated
/* 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(function_name), &retval, num_params, params);
if (UNEXPECTED(method_call_result == FAILURE)) {
zend_throw_error(NULL, "Call to undefined method %s::%s()", ZSTR_VAL(Z_OBJCE_P(soap_obj)->name), Z_STRVAL(function_name));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we kind of lose precision, whether it really does not exist or the method is private. I m unsure but maybe zend_is_callable* might fit better wdyt ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd rather have a new zend_call_method_if_exists_ex() API which has an char **error which handles the error message.

I'll do this in a different PR and then rebase. :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devnexen Let me know if you prefer this approach with the new API.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes it makes sense

Comment thread ext/soap/tests/SoapServer/persistent-session-no-free-SoapFault.phpt
@Girgias
Girgias force-pushed the soap-call-func-directly branch from 4f51fe0 to e279103 Compare October 2, 2026 17:04
@Girgias
Girgias requested a review from dstogov as a code owner October 2, 2026 17:04
@Girgias
Girgias merged commit 1c4fbed into php:master Oct 2, 2026
18 checks passed
@Girgias
Girgias deleted the soap-call-func-directly branch October 2, 2026 18:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants