diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index e85b4ea42250..8df6a5599d30 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -3757,6 +3757,11 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string if (ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE) { resolve_delayed_variance_obligations(ce); } + /* Delayed variance resolution can re-enter linking before the full + * hierarchy is linked. See ext/opcache/tests/gh20469*.phpt. */ + if (CG(unlinked_uses) && zend_hash_index_exists(CG(unlinked_uses), (zend_long)(uintptr_t) ce)) { + ce->ce_flags &= ~ZEND_ACC_CACHEABLE; + } if (ce->ce_flags & ZEND_ACC_CACHEABLE) { ce->ce_flags &= ~ZEND_ACC_CACHEABLE; } else { @@ -3764,6 +3769,7 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string } } + bool was_cacheable = is_cacheable; if (!CG(current_linking_class)) { is_cacheable = 0; } @@ -3784,6 +3790,13 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string zend_hash_destroy(ht); FREE_HASHTABLE(ht); } + } else if (was_cacheable && ce->inheritance_cache) { + /* Cacheability can be disabled after dependency tracking prepared + * an inheritance-cache dependency table. Discard it here. */ + HashTable *ht = (HashTable*)ce->inheritance_cache; + ce->inheritance_cache = NULL; + zend_hash_destroy(ht); + FREE_HASHTABLE(ht); } if (!orig_record_errors) { diff --git a/ext/opcache/tests/gh20469.phpt b/ext/opcache/tests/gh20469.phpt new file mode 100644 index 000000000000..1cd826c177ef --- /dev/null +++ b/ext/opcache/tests/gh20469.phpt @@ -0,0 +1,134 @@ +--TEST-- +GH-20469: Inheritance cache with reentrant autoloading must not crash +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + ParentBeingLinked -> CovariantReturnWithTrait + * -> RequiresRootReturnTrait -> ChildOfParentBeingLinked. + */ +file_put_contents($dir . '/test1.php', <<<'PHP' + +--CLEAN-- +isDir()) { + rmdir($file->getPathname()); + } else { + unlink($file->getPathname()); + } + } + rmdir($dir); +} +?> +--EXPECT-- +3 +3 diff --git a/ext/opcache/tests/gh20469_child_variance_resolves_parent.phpt b/ext/opcache/tests/gh20469_child_variance_resolves_parent.phpt new file mode 100644 index 000000000000..4a66c3b0513d --- /dev/null +++ b/ext/opcache/tests/gh20469_child_variance_resolves_parent.phpt @@ -0,0 +1,181 @@ +--TEST-- +GH-20469: Child delayed variance can resolve parent before direct delayed resolution +--DESCRIPTION-- +This variant ensures the cacheability check after load_delayed_classes() is +needed. Loading the delayed child resolves the parent class's variance +obligations reentrantly, so the parent no longer has ZEND_ACC_UNRESOLVED_VARIANCE +when control returns from load_delayed_classes(). The parent was still used while +nearly linked, and must not be inserted into the inheritance cache. +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + CovariantReturnWithTrait -> RequiresRootReturnTrait + * -> ChildOfParentBeingLinked -> ParentBeingLinked. + * + * ChildOfParentBeingLinked also has delayed variance, so resolving the child's + * dependency on ParentBeingLinked can resolve ParentBeingLinked before it + * reaches its direct resolve_delayed_variance_obligations() call. + */ +file_put_contents($dir . '/test1.php', <<<'PHP' +test()); +PHP); + +file_put_contents($dir . '/test3.php', <<<'PHP' +test()); +PHP); + +file_put_contents($dir . '/classes/RootForTraitReturn.php', <<<'PHP' + +--CLEAN-- +isDir()) { + rmdir($file->getPathname()); + } else { + unlink($file->getPathname()); + } + } + rmdir($dir); +} +?> +--EXPECT-- +3 +3NULL +3NULL diff --git a/ext/opcache/tests/gh20469_inheritance_cache_cleanup.phpt b/ext/opcache/tests/gh20469_inheritance_cache_cleanup.phpt new file mode 100644 index 000000000000..aabbc398cbc3 --- /dev/null +++ b/ext/opcache/tests/gh20469_inheritance_cache_cleanup.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-20469: Skipped inheritance cache cleanup must ignore non-cacheable classes +--DESCRIPTION-- +Autoloading the parent makes the child use the runtime class-linking path, but +the child does not enter inheritance-cache construction. Under ASAN, the +uninitialized inheritance_cache field is filled with non-zero bytes. Skipped +cache insertion must not treat that value as a temporary dependency table. +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECT-- +ok diff --git a/ext/opcache/tests/gh20469_inherited_method.phpt b/ext/opcache/tests/gh20469_inherited_method.phpt new file mode 100644 index 000000000000..f3c038bdc330 --- /dev/null +++ b/ext/opcache/tests/gh20469_inherited_method.phpt @@ -0,0 +1,138 @@ +--TEST-- +GH-20469: Inheritance cache with reentrant autoloading must preserve inherited methods +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + ParentBeingLinked -> CovariantReturnWithTrait + * -> RequiresRootReturnTrait -> ChildOfParentBeingLinked. + */ +file_put_contents($dir . '/test1.php', <<<'PHP' +test()); +PHP); + +file_put_contents($dir . '/classes/RootForTraitReturn.php', <<<'PHP' + +--CLEAN-- +isDir()) { + rmdir($file->getPathname()); + } else { + unlink($file->getPathname()); + } + } + rmdir($dir); +} +?> +--EXPECT-- +3 +3NULL diff --git a/ext/soap/soap.c b/ext/soap/soap.c index 8a103e09636d..83584283740d 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -1414,7 +1414,7 @@ PHP_METHOD(SoapServer, handle) } } - if ((soap_action_z = zend_hash_str_find(Z_ARRVAL_P(server_vars), ZEND_STRL("HTTP_SOAPACTION"))) != NULL && Z_TYPE_P(soap_action_z) == IS_STRING) { + if ((soap_action_z = zend_hash_str_find(Z_ARRVAL_P(server_vars), ZEND_STRL("HTTP_SOAPACTION"))) != NULL && Z_TYPE_P(soap_action_z) == IS_STRING && Z_STRLEN_P(soap_action_z) > 0) { soap_action = Z_STRVAL_P(soap_action_z); } } @@ -3179,6 +3179,10 @@ static sdlFunctionPtr find_function_using_soap_action(const sdl *sdl, const char soap_action_length -= 2; } + if (UNEXPECTED(soap_action_length == 0)) { + return NULL; + } + /* TODO: This may depend on a particular target namespace, in which case this won't find a match when multiple different * target namespaces are used until #45282 is resolved. */ sdlFunctionPtr function; diff --git a/ext/soap/tests/bugs/gh22285.phpt b/ext/soap/tests/bugs/gh22285.phpt new file mode 100644 index 000000000000..8c7e0933588e --- /dev/null +++ b/ext/soap/tests/bugs/gh22285.phpt @@ -0,0 +1,45 @@ +--TEST-- +GH-22285 (SoapServer dispatches to the first function when the SOAPAction header is empty) +--CREDITS-- +Jarkko Hyvärinen +--EXTENSIONS-- +soap +--INI-- +soap.wsdl_cache_enabled=0 +--SKIPIF-- + +--POST-- + + + + World + + + +--FILE-- + 'Hello ' . $params->name]; + } + public function goodbye($params) { + return ['message' => 'Goodbye ' . $params->name]; + } +} + +$server = new SoapServer(__DIR__ . '/gh22285.wsdl', [ + 'cache_wsdl' => WSDL_CACHE_NONE, + 'encoding' => 'UTF-8', + 'soap_version' => SOAP_1_1, +]); +$server->setClass('TestWS'); +$_SERVER['HTTP_SOAPACTION'] = '""'; +$server->handle(); +?> +--EXPECTF-- + +Goodbye World diff --git a/ext/soap/tests/bugs/gh22285.wsdl b/ext/soap/tests/bugs/gh22285.wsdl new file mode 100644 index 000000000000..66b9e6749071 --- /dev/null +++ b/ext/soap/tests/bugs/gh22285.wsdl @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +