diff --git a/Zend/tests/type_declarations/variance/class_order_autoload7.phpt b/Zend/tests/type_declarations/variance/class_order_autoload7.phpt new file mode 100644 index 0000000000000..b12e312f16896 --- /dev/null +++ b/Zend/tests/type_declarations/variance/class_order_autoload7.phpt @@ -0,0 +1,41 @@ +--TEST-- +GH-20112 (Nested load_delayed_classes must not process unrelated outer entries) +--FILE-- + +--EXPECT-- +OK diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 5e93c2f864eca..386b0b823b771 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -457,6 +457,7 @@ void init_compiler(void) /* {{{ */ CG(delayed_variance_obligations) = NULL; CG(delayed_autoloads) = NULL; + CG(delayed_autoloads_depth) = 0; CG(unlinked_uses) = NULL; CG(current_linking_class) = NULL; } diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 62a97d753634a..403c12b01b413 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -149,6 +149,7 @@ struct _zend_compiler_globals { HashTable *delayed_variance_obligations; HashTable *delayed_autoloads; + uint32_t delayed_autoloads_depth; HashTable *unlinked_uses; zend_class_entry *current_linking_class; diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 56e5bdb9295a6..01ec3e9c0f579 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -3275,11 +3275,19 @@ static void load_delayed_classes(zend_class_entry *ce) { return; } - /* Autoloading can trigger linking of another class, which may register new delayed autoloads. - * For that reason, this code uses a loop that pops and loads the first element of the HT. If - * this triggers linking, then the remaining classes may get loaded when linking the newly - * loaded class. This is important, as otherwise necessary dependencies may not be available - * if the new class is lower in the hierarchy than the current one. */ + /* Autoloading can trigger linking of another class, which may register new delayed + * autoloads. For that reason, this code uses a loop that pops and loads the first + * element of the HT. If this triggers linking, then the remaining classes may get + * loaded when linking the newly loaded class. This is important, as otherwise + * necessary dependencies may not be available if the new class is lower in the + * hierarchy than the current one. + * + * However, when load_delayed_classes() is called recursively, the shared table may + * contain entries from an outer caller whose dependencies are not yet available. + * Loading such an entry would fail. In nested calls, such failures are caught and + * the entry is re-appended for the outer caller to process later (GH-20112). */ + CG(delayed_autoloads_depth)++; + uint32_t consecutive_failures = 0; HashPosition pos = 0; zend_string *name; zend_ulong idx; @@ -3288,13 +3296,34 @@ static void load_delayed_classes(zend_class_entry *ce) { zend_string_addref(name); zend_hash_del(delayed_autoloads, name); zend_lookup_class(name); - zend_string_release(name); if (EG(exception)) { + if (CG(delayed_autoloads_depth) > 1) { + zend_string *lc_name = zend_string_tolower(name); + bool class_was_loaded = zend_hash_exists(CG(class_table), lc_name); + zend_string_release(lc_name); + if (!class_was_loaded) { + /* Nested call: class could not be loaded, likely because a + * dependency higher in the call chain is not yet available. + * Re-append for the outer caller to process later. */ + zend_clear_exception(); + zend_hash_add_empty_element(delayed_autoloads, name); + zend_string_release(name); + consecutive_failures++; + if (consecutive_failures >= zend_hash_num_elements(delayed_autoloads)) { + break; + } + continue; + } + } + CG(delayed_autoloads_depth)--; zend_exception_uncaught_error( "During inheritance of %s, while autoloading %s", ZSTR_VAL(ce->name), ZSTR_VAL(name)); } + zend_string_release(name); + consecutive_failures = 0; } + CG(delayed_autoloads_depth)--; } static void resolve_delayed_variance_obligations(zend_class_entry *ce) {