Skip to content

Fix GH-10026: Garbage collection stops after exception in SoapClient #10283

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: PHP-8.1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -1175,20 +1175,24 @@ ZEND_COLD void zenderror(const char *error) /* {{{ */
}
/* }}} */

ZEND_API ZEND_COLD ZEND_NORETURN void _zend_bailout(const char *filename, uint32_t lineno) /* {{{ */
ZEND_API ZEND_COLD ZEND_NORETURN void _zend_bailout_without_gc_protect(const char *filename, uint32_t lineno)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we could pick a more generic name, like _zend_nonfatal_bailout to make it more clear which is to be used.

{

if (!EG(bailout)) {
zend_output_debug_string(1, "%s(%d) : Bailed out without a bailout address!", filename, lineno);
exit(-1);
}
gc_protect(1);
CG(unclean_shutdown) = 1;
CG(active_class_entry) = NULL;
CG(in_compilation) = 0;
EG(current_execute_data) = NULL;
LONGJMP(*EG(bailout), FAILURE);
}

ZEND_API ZEND_COLD ZEND_NORETURN void _zend_bailout(const char *filename, uint32_t lineno) /* {{{ */
{
gc_protect(true);
_zend_bailout_without_gc_protect(filename, lineno);
}
/* }}} */

ZEND_API size_t zend_get_page_size(void)
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ typedef struct _zend_utility_values {

typedef size_t (*zend_write_func_t)(const char *str, size_t str_length);

#define zend_bailout_without_gc_protect() _zend_bailout_without_gc_protect(__FILE__, __LINE__)
#define zend_bailout() _zend_bailout(__FILE__, __LINE__)

#define zend_try \
Expand All @@ -274,6 +275,7 @@ void zend_register_standard_ini_entries(void);
zend_result zend_post_startup(void);
void zend_set_utility_values(zend_utility_values *utility_values);

ZEND_API ZEND_COLD ZEND_NORETURN void _zend_bailout_without_gc_protect(const char *filename, uint32_t lineno);
ZEND_API ZEND_COLD ZEND_NORETURN void _zend_bailout(const char *filename, uint32_t lineno);
ZEND_API size_t zend_get_page_size(void);

Expand Down
2 changes: 1 addition & 1 deletion ext/oci8/php_oci8_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ typedef struct {
ub4 serverStatus = OCI_SERVER_NORMAL; \
switch (errcode) { \
case 1013: \
zend_bailout(); \
zend_bailout_without_gc_protect(); \
break; \
case 22: \
case 28: \
Expand Down
2 changes: 1 addition & 1 deletion ext/pdo_oci/oci_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ ub4 _oci_error(OCIError *err, pdo_dbh_t *dbh, pdo_stmt_t *stmt, char *what, swor
if (einfo->errcode) {
switch (einfo->errcode) {
case 1013: /* user requested cancel of current operation */
zend_bailout();
zend_bailout_without_gc_protect();
break;

case 12154: /* ORA-12154: TNS:could not resolve service name */
Expand Down
16 changes: 8 additions & 8 deletions ext/phar/phar_object.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ static int phar_file_action(phar_archive_data *phar, phar_entry_info *info, char
#ifdef PHP_WIN32
efree(arch);
#endif
zend_bailout();
zend_bailout_without_gc_protect();
case PHAR_MIME_OTHER:
/* send headers, output file contents */
efree(basename);
Expand All @@ -176,7 +176,7 @@ static int phar_file_action(phar_archive_data *phar, phar_entry_info *info, char
efree((void *) ctr.line);

if (FAILURE == sapi_send_headers()) {
zend_bailout();
zend_bailout_without_gc_protect();
}

/* prepare to output */
Expand Down Expand Up @@ -207,7 +207,7 @@ static int phar_file_action(phar_archive_data *phar, phar_entry_info *info, char
}
} while (1);

zend_bailout();
zend_bailout_without_gc_protect();
case PHAR_MIME_PHP:
if (basename) {
phar_mung_server_vars(arch, entry, entry_len, basename, ru_len);
Expand Down Expand Up @@ -283,7 +283,7 @@ static int phar_file_action(phar_archive_data *phar, phar_entry_info *info, char
efree(name);
} zend_end_try();

zend_bailout();
zend_bailout_without_gc_protect();
}

return PHAR_MIME_PHP;
Expand Down Expand Up @@ -705,7 +705,7 @@ PHP_METHOD(Phar, webPhar)
}
efree(pt);

zend_bailout();
zend_bailout_without_gc_protect();
default:
zend_throw_exception_ex(phar_ce_PharException, 0, "phar error: rewrite callback must return a string or false");

Expand Down Expand Up @@ -751,7 +751,7 @@ PHP_METHOD(Phar, webPhar)
efree(path_info);
}

zend_bailout();
zend_bailout_without_gc_protect();
} else {
char *tmp = NULL, sa = '\0';
sapi_header_line ctr = {0};
Expand Down Expand Up @@ -785,14 +785,14 @@ PHP_METHOD(Phar, webPhar)
sapi_header_op(SAPI_HEADER_REPLACE, &ctr);
sapi_send_headers();
efree((void *) ctr.line);
zend_bailout();
zend_bailout_without_gc_protect();
}
}

if (FAILURE == phar_get_archive(&phar, fname, fname_len, NULL, 0, NULL) ||
(info = phar_get_entry_info(phar, entry, entry_len, NULL, 0)) == NULL) {
phar_do_404(phar, fname, fname_len, f404, f404_len, entry, entry_len);
zend_bailout();
zend_bailout_without_gc_protect();
}

if (mimeoverride && zend_hash_num_elements(Z_ARRVAL_P(mimeoverride))) {
Expand Down
2 changes: 1 addition & 1 deletion ext/soap/php_encoding.c
Original file line number Diff line number Diff line change
Expand Up @@ -1518,7 +1518,7 @@ static zval *to_zval_object_ex(zval *ret, encodeTypePtr type, xmlNodePtr data, z
master_to_zval(&data, attr->encode, dummy);
} zend_catch {
xmlFreeNode(dummy);
zend_bailout();
zend_bailout_without_gc_protect();
} zend_end_try();
xmlFreeNode(dummy);
set_zval_property(ret, attr->name, &data);
Expand Down
2 changes: 1 addition & 1 deletion ext/soap/php_sdl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1172,7 +1172,7 @@ static sdlPtr load_wsdl(zval *this_ptr, char *struri)
} zend_catch {
/* Avoid persistent memory leak. */
zend_hash_destroy(&ctx.docs);
zend_bailout();
zend_bailout_without_gc_protect();
} zend_end_try();

zend_hash_destroy(&ctx.messages);
Expand Down
18 changes: 9 additions & 9 deletions ext/soap/soap.c
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ static void soap_error_handler(int error_num, zend_string *error_filename, const
Z_OBJ(SOAP_GLOBAL(error_object)) = _old_error_object;\
SOAP_GLOBAL(soap_version) = _old_soap_version;\
if (_bailout) {\
zend_bailout();\
zend_bailout_without_gc_protect();\
}

#define FETCH_THIS_SDL(ss) \
Expand Down Expand Up @@ -1356,7 +1356,7 @@ PHP_METHOD(SoapServer, handle)
} zend_catch {
/* Avoid leaking persistent memory */
xmlFreeDoc(doc_request);
zend_bailout();
zend_bailout_without_gc_protect();
} zend_end_try();

xmlFreeDoc(doc_request);
Expand Down Expand Up @@ -1767,7 +1767,7 @@ static ZEND_NORETURN void soap_server_fault(char* code, char* string, char *acto
set_soap_fault(&ret, NULL, code, string, actor, details, name);
/* TODO: Which function */
soap_server_fault_ex(NULL, &ret, NULL);
zend_bailout();
zend_bailout_without_gc_protect();
}
/* }}} */

Expand Down Expand Up @@ -1797,7 +1797,7 @@ static zend_never_inline ZEND_COLD void soap_real_error_handler(int error_num, z
add_soap_fault_ex(&fault, &SOAP_GLOBAL(error_object), code, ZSTR_VAL(message), NULL, NULL);
Z_ADDREF(fault);
zend_throw_exception_object(&fault);
zend_bailout();
zend_bailout_without_gc_protect();
} else if (!use_exceptions ||
!SOAP_GLOBAL(error_code) ||
strcmp(SOAP_GLOBAL(error_code),"WSDL") != 0) {
Expand Down Expand Up @@ -1859,7 +1859,7 @@ static zend_never_inline ZEND_COLD void soap_real_error_handler(int error_num, z

if (fault) {
soap_server_fault_ex(NULL, &fault_obj, NULL);
zend_bailout();
zend_bailout_without_gc_protect();
}
}
}
Expand Down Expand Up @@ -2187,7 +2187,7 @@ static int do_request(zval *this_ptr, xmlDoc *request, char *location, char *act
zval_ptr_dtor(&params[0]);
xmlFree(buf);
if (_bailout) {
zend_bailout();
zend_bailout_without_gc_protect();
}
if (ret && Z_TYPE_P(Z_CLIENT_SOAP_FAULT_P(this_ptr)) == IS_OBJECT) {
ret = FALSE;
Expand Down Expand Up @@ -2407,7 +2407,7 @@ static void do_soap_call(zend_execute_data *execute_data,
if (request) {
xmlFreeDoc(request);
}
zend_bailout();
zend_bailout_without_gc_protect();
}
SOAP_CLIENT_END_CODE();
}
Expand Down Expand Up @@ -3749,7 +3749,7 @@ static xmlDocPtr serialize_response_call(sdlFunctionPtr function, char *function
} zend_catch {
/* Avoid persistent memory leak. */
xmlFreeDoc(doc);
zend_bailout();
zend_bailout_without_gc_protect();
} zend_end_try();

if (function && function->responseName == NULL &&
Expand Down Expand Up @@ -3954,7 +3954,7 @@ static xmlDocPtr serialize_function_call(zval *this_ptr, sdlFunctionPtr function
} zend_catch {
/* Avoid persistent memory leak. */
xmlFreeDoc(doc);
zend_bailout();
zend_bailout_without_gc_protect();
} zend_end_try();

return doc;
Expand Down
65 changes: 65 additions & 0 deletions ext/soap/tests/gh10026.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
--TEST--
GH-10026 (Garbage collection stops after exception in SoapClient)
--EXTENSIONS--
soap
--FILE--
<?php

class A
{
public $b;
}

class B
{
public $a;
}

function buildCycle()
{
$a = new A();
$b = new B();
$a->b = $b;
$b->a = $a;
}

function failySoapCall()
{
try {
new SoapClient('https://127.0.0.1/?WSDL');
} catch (Exception) {
echo "Soap call failed\n";
}
}

function gc()
{
gc_collect_cycles();
echo "GC Runs: " . gc_status()['runs'] . "\n";
}


buildCycle();
gc();
buildCycle();
gc();
buildCycle();
gc();

failySoapCall();

buildCycle();
gc();
buildCycle();
gc();
buildCycle();
gc();
?>
--EXPECT--
GC Runs: 1
GC Runs: 2
GC Runs: 3
Soap call failed
GC Runs: 4
GC Runs: 5
GC Runs: 6
2 changes: 1 addition & 1 deletion sapi/fuzzer/fuzzer-execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ static zend_always_inline void fuzzer_step(void) {
/* Reset steps before bailing out, so code running after bailout (e.g. in
* destructors) will get another MAX_STEPS, rather than UINT32_MAX steps. */
steps_left = MAX_STEPS;
zend_bailout();
zend_bailout_without_gc_protect();
}
}

Expand Down
2 changes: 1 addition & 1 deletion sapi/phpdbg/phpdbg_prompt.c
Original file line number Diff line number Diff line change
Expand Up @@ -1549,7 +1549,7 @@ int phpdbg_interactive(bool allow_async_unsafe, char *input) /* {{{ */
ret = phpdbg_stack_execute(&stack, allow_async_unsafe);
} zend_catch {
phpdbg_stack_free(&stack);
zend_bailout();
zend_bailout_without_gc_protect();
} zend_end_try();

switch (ret) {
Expand Down