diff --git a/NEWS b/NEWS index 49b61d236967..fea16290736c 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,10 @@ PHP NEWS IntlCalendar::equals(), ::before(), ::after(), and ::isEquivalentTo(). (Weilin Du) +- Opcache: + . Fixed bug GH-20469 (Crash during inheritance cache lookup with nested + autoloading). (Levi Morrison) + - Phar: . Fixed a bypass of the magic ".phar" directory protection in Phar::addEmptyDir() for paths starting with "/.phar", while allowing diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index eba21dd8e82a..8846633559d7 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -310,14 +310,23 @@ static zend_class_entry *lookup_class(zend_class_entry *scope, zend_string *name /* Instanceof that's safe to use on unlinked classes. */ static bool unlinked_instanceof(zend_class_entry *ce1, const zend_class_entry *ce2) { + /* Do not short-circuit to instanceof_function() here: + * + * if (ce1->ce_flags & ZEND_ACC_LINKED) { + * return instanceof_function(ce1, ce2); + * } + * + * See ext/opcache/tests/gh20469.phpt. During inheritance cache lookups, + * autoloading may re-enter class linking in a way where ce1 itself is + * linked, but a class in its parent chain is still unlinked. The normal + * instanceof_function() assumes the whole parent chain is linked, and may + * interpret an unresolved parent_name as a zend_class_entry (and crash). + */ + if (ce1 == ce2) { return 1; } - if (ce1->ce_flags & ZEND_ACC_LINKED) { - return instanceof_function(ce1, ce2); - } - if (ce1->parent) { zend_class_entry *parent_ce; if (ce1->ce_flags & ZEND_ACC_RESOLVED_PARENT) { 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