Skip to content

Instantly share code, notes, and snippets.

@adsr
Created June 24, 2023 18:31
Show Gist options
  • Save adsr/fcd1790b59cc5d8ea66b0235c933649f to your computer and use it in GitHub Desktop.
Save adsr/fcd1790b59cc5d8ea66b0235c933649f to your computer and use it in GitHub Desktop.
diff --git a/Zend/tests/is_in_autoload.phpt b/Zend/tests/is_in_autoload.phpt
new file mode 100644
index 0000000000..29d4ca7ccc
--- /dev/null
+++ b/Zend/tests/is_in_autoload.phpt
@@ -0,0 +1,49 @@
+--TEST--
+Test is_in_autoload() functionality
+--FILE--
+<?php
+
+$depth = 0;
+
+function report($tag) {
+ global $depth;
+ printf(
+ "%-5s depth=%d is_in_autoload=%s A=%s \A=%s B=%s \B=%s C=%s \C=%s\n",
+ $tag,
+ $depth,
+ is_in_autoload() ? 'y' : 'n',
+ is_in_autoload('A') ? 'y' : 'n',
+ is_in_autoload('\A') ? 'y' : 'n',
+ is_in_autoload('B') ? 'y' : 'n',
+ is_in_autoload('\B') ? 'y' : 'n',
+ is_in_autoload('C') ? 'y' : 'n',
+ is_in_autoload('\C') ? 'y' : 'n',
+ );
+}
+
+spl_autoload_register(function ($name) use (&$depth) {
+ $depth += 1;
+ report("pre{$name}");
+ if ($name === 'A') {
+ class_exists('B');
+ } else if ($name === 'B') {
+ class_exists('C');
+ }
+ report("post{$name}");
+ $depth -= 1;
+});
+
+report('init');
+class_exists('\A');
+report('final');
+
+?>
+--EXPECT--
+init depth=0 is_in_autoload=n A=n \A=n B=n \B=n C=n \C=n
+preA depth=1 is_in_autoload=y A=y \A=y B=n \B=n C=n \C=n
+preB depth=2 is_in_autoload=y A=y \A=y B=y \B=y C=n \C=n
+preC depth=3 is_in_autoload=y A=y \A=y B=y \B=y C=y \C=y
+postC depth=3 is_in_autoload=y A=y \A=y B=y \B=y C=y \C=y
+postB depth=2 is_in_autoload=y A=y \A=y B=y \B=y C=n \C=n
+postA depth=1 is_in_autoload=y A=y \A=y B=n \B=n C=n \C=n
+final depth=0 is_in_autoload=n A=n \A=n B=n \B=n C=n \C=n
diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c
index acf69536d4..2dd3d7ca5a 100644
--- a/Zend/zend_builtin_functions.c
+++ b/Zend/zend_builtin_functions.c
@@ -659,20 +659,51 @@ ZEND_FUNCTION(is_subclass_of)
}
/* }}} */
/* {{{ Returns true if the first argument is an object and is this class or has this class as one of its parents, */
ZEND_FUNCTION(is_a)
{
is_a_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */
+/* {{{ Returns true if in autoload, optionally for a specific class */
+ZEND_FUNCTION(is_in_autoload)
+{
+ zend_string *class_name = NULL;
+ zend_string *lc_name;
+ bool is_in_autoload;
+
+ ZEND_PARSE_PARAMETERS_START(0, 1)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_STR_OR_NULL(class_name)
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (class_name) {
+ if (ZSTR_VAL(class_name)[0] == '\\') {
+ lc_name = zend_string_alloc(ZSTR_LEN(class_name) - 1, 0);
+ zend_str_tolower_copy(ZSTR_VAL(lc_name), ZSTR_VAL(class_name) + 1, ZSTR_LEN(class_name) - 1);
+ } else {
+ lc_name = zend_string_tolower(class_name);
+ }
+ is_in_autoload = zend_is_in_autoload(lc_name);
+ zend_string_release_ex(lc_name, 0);
+ if (is_in_autoload) {
+ RETURN_TRUE;
+ }
+ } else if (zend_in_autoload_count() > 0) {
+ RETURN_TRUE;
+ }
+ RETURN_FALSE;
+}
+/* }}} */
+
/* {{{ add_class_vars */
static void add_class_vars(zend_class_entry *scope, zend_class_entry *ce, bool statics, zval *return_value)
{
zend_property_info *prop_info;
zval *prop, prop_copy;
zend_string *key;
zval *default_properties_table = CE_DEFAULT_PROPERTIES_TABLE(ce);
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->properties_info, key, prop_info) {
if (((prop_info->flags & ZEND_ACC_PROTECTED) &&
diff --git a/Zend/zend_builtin_functions.stub.php b/Zend/zend_builtin_functions.stub.php
index 94d356e790..c38ce5e32f 100644
--- a/Zend/zend_builtin_functions.stub.php
+++ b/Zend/zend_builtin_functions.stub.php
@@ -42,20 +42,22 @@ function get_class(object $object = UNKNOWN): string {}
function get_called_class(): string {}
function get_parent_class(object|string $object_or_class = UNKNOWN): string|false {}
/** @param object|string $object_or_class */
function is_subclass_of(mixed $object_or_class, string $class, bool $allow_string = true): bool {}
/** @param object|string $object_or_class */
function is_a(mixed $object_or_class, string $class, bool $allow_string = false): bool {}
+function is_in_autoload(string $class = null): bool {}
+
/**
* @return array<string, mixed|ref>
* @refcount 1
*/
function get_class_vars(string $class): array {}
function get_object_vars(object $object): array {}
function get_mangled_object_vars(object $object): array {}
diff --git a/Zend/zend_builtin_functions_arginfo.h b/Zend/zend_builtin_functions_arginfo.h
index c39f847871..1096a205c6 100644
--- a/Zend/zend_builtin_functions_arginfo.h
+++ b/Zend/zend_builtin_functions_arginfo.h
@@ -1,12 +1,12 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 73e9ef76bde5ab44254185175d4d8dae2e797d12 */
+ * Stub hash: e55c3b5b817c50603b5d5262f5d7be19ae303a2f */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_version, 0, 0, IS_STRING, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_func_num_args, 0, 0, IS_LONG, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_func_get_arg, 0, 1, IS_MIXED, 0)
ZEND_ARG_TYPE_INFO(0, position, IS_LONG, 0)
ZEND_END_ARG_INFO()
@@ -62,20 +62,24 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_is_subclass_of, 0, 2, _IS_BOOL,
ZEND_ARG_TYPE_INFO(0, class, IS_STRING, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, allow_string, _IS_BOOL, 0, "true")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_is_a, 0, 2, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, object_or_class, IS_MIXED, 0)
ZEND_ARG_TYPE_INFO(0, class, IS_STRING, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, allow_string, _IS_BOOL, 0, "false")
ZEND_END_ARG_INFO()
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_is_in_autoload, 0, 0, _IS_BOOL, 0)
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, class, IS_STRING, 0, "null")
+ZEND_END_ARG_INFO()
+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_get_class_vars, 0, 1, IS_ARRAY, 0)
ZEND_ARG_TYPE_INFO(0, class, IS_STRING, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_get_object_vars, 0, 1, IS_ARRAY, 0)
ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0)
ZEND_END_ARG_INFO()
#define arginfo_get_mangled_object_vars arginfo_get_object_vars
@@ -228,20 +232,21 @@ ZEND_FUNCTION(strncmp);
ZEND_FUNCTION(strcasecmp);
ZEND_FUNCTION(strncasecmp);
ZEND_FUNCTION(error_reporting);
ZEND_FUNCTION(define);
ZEND_FUNCTION(defined);
ZEND_FUNCTION(get_class);
ZEND_FUNCTION(get_called_class);
ZEND_FUNCTION(get_parent_class);
ZEND_FUNCTION(is_subclass_of);
ZEND_FUNCTION(is_a);
+ZEND_FUNCTION(is_in_autoload);
ZEND_FUNCTION(get_class_vars);
ZEND_FUNCTION(get_object_vars);
ZEND_FUNCTION(get_mangled_object_vars);
ZEND_FUNCTION(get_class_methods);
ZEND_FUNCTION(method_exists);
ZEND_FUNCTION(property_exists);
ZEND_FUNCTION(class_exists);
ZEND_FUNCTION(interface_exists);
ZEND_FUNCTION(trait_exists);
ZEND_FUNCTION(enum_exists);
@@ -289,20 +294,21 @@ static const zend_function_entry ext_functions[] = {
ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(strcasecmp, arginfo_strcasecmp)
ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(strncasecmp, arginfo_strncasecmp)
ZEND_FE(error_reporting, arginfo_error_reporting)
ZEND_FE(define, arginfo_define)
ZEND_FE(defined, arginfo_defined)
ZEND_FE(get_class, arginfo_get_class)
ZEND_FE(get_called_class, arginfo_get_called_class)
ZEND_FE(get_parent_class, arginfo_get_parent_class)
ZEND_FE(is_subclass_of, arginfo_is_subclass_of)
ZEND_FE(is_a, arginfo_is_a)
+ ZEND_FE(is_in_autoload, arginfo_is_in_autoload)
ZEND_FE(get_class_vars, arginfo_get_class_vars)
ZEND_FE(get_object_vars, arginfo_get_object_vars)
ZEND_FE(get_mangled_object_vars, arginfo_get_mangled_object_vars)
ZEND_FE(get_class_methods, arginfo_get_class_methods)
ZEND_FE(method_exists, arginfo_method_exists)
ZEND_FE(property_exists, arginfo_property_exists)
ZEND_FE(class_exists, arginfo_class_exists)
ZEND_FE(interface_exists, arginfo_interface_exists)
ZEND_FE(trait_exists, arginfo_trait_exists)
ZEND_FE(enum_exists, arginfo_enum_exists)
diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h
index f2cb676478..7cbb4e8ece 100644
--- a/Zend/zend_execute.h
+++ b/Zend/zend_execute.h
@@ -29,20 +29,23 @@
#include <stdint.h>
BEGIN_EXTERN_C()
struct _zend_fcall_info;
ZEND_API extern void (*zend_execute_ex)(zend_execute_data *execute_data);
ZEND_API extern void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value);
/* The lc_name may be stack allocated! */
ZEND_API extern zend_class_entry *(*zend_autoload)(zend_string *name, zend_string *lc_name);
+ZEND_API uint32_t zend_in_autoload_count(void);
+ZEND_API bool zend_is_in_autoload(zend_string *lc_name);
+
void init_executor(void);
void shutdown_executor(void);
void shutdown_destructors(void);
ZEND_API void zend_shutdown_executor_values(bool fast_shutdown);
ZEND_API void zend_init_execute_data(zend_execute_data *execute_data, zend_op_array *op_array, zval *return_value);
ZEND_API void zend_init_func_execute_data(zend_execute_data *execute_data, zend_op_array *op_array, zval *return_value);
ZEND_API void zend_init_code_execute_data(zend_execute_data *execute_data, zend_op_array *op_array, zval *return_value);
ZEND_API void zend_execute(zend_op_array *op_array, zval *return_value);
ZEND_API void execute_ex(zend_execute_data *execute_data);
diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c
index dfb13005f6..e55b9e2b53 100644
--- a/Zend/zend_execute_API.c
+++ b/Zend/zend_execute_API.c
@@ -260,20 +260,48 @@ void shutdown_destructors(void) /* {{{ */
zend_hash_reverse_apply(&EG(symbol_table), (apply_func_t) zval_call_destructor);
} while (symbols != zend_hash_num_elements(&EG(symbol_table)));
zend_objects_store_call_destructors(&EG(objects_store));
} zend_catch {
/* if we couldn't destruct cleanly, mark all objects as destructed anyway */
zend_objects_store_mark_destructed(&EG(objects_store));
} zend_end_try();
}
/* }}} */
+static zend_result zend_clear_in_autoload(zend_string *lc_name) /* {{{ */
+{
+ if (!EG(in_autoload)) {
+ return FAILURE;
+ }
+ return zend_hash_del(EG(in_autoload), lc_name);
+}
+/* }}} */
+
+static zend_result zend_set_in_autoload(zend_string *lc_name) /* {{{ */
+{
+ if (!EG(in_autoload)) {
+ ALLOC_HASHTABLE(EG(in_autoload));
+ zend_hash_init(EG(in_autoload), 8, NULL, NULL, 0);
+ }
+ return zend_hash_add_empty_element(EG(in_autoload), lc_name) ? SUCCESS : FAILURE;
+}
+/* }}} */
+
+static void zend_in_autoload_destroy(void) /* {{{ */
+{
+ if (EG(in_autoload)) {
+ zend_hash_destroy(EG(in_autoload));
+ FREE_HASHTABLE(EG(in_autoload));
+ }
+}
+/* }}} */
+
/* Free values held by the executor. */
ZEND_API void zend_shutdown_executor_values(bool fast_shutdown)
{
zend_string *key;
zval *zv;
EG(flags) |= EG_FLAGS_IN_RESOURCE_SHUTDOWN;
zend_try {
zend_close_rsrc_list(&EG(regular_list));
} zend_end_try();
@@ -459,24 +487,21 @@ void shutdown_executor(void) /* {{{ */
zend_hash_destroy(*EG(symtable_cache_ptr));
FREE_HASHTABLE(*EG(symtable_cache_ptr));
}
zend_hash_destroy(&EG(included_files));
zend_stack_destroy(&EG(user_error_handlers_error_reporting));
zend_stack_destroy(&EG(user_error_handlers));
zend_stack_destroy(&EG(user_exception_handlers));
zend_objects_store_destroy(&EG(objects_store));
- if (EG(in_autoload)) {
- zend_hash_destroy(EG(in_autoload));
- FREE_HASHTABLE(EG(in_autoload));
- }
+ zend_in_autoload_destroy();
if (EG(ht_iterators) != EG(ht_iterators_slots)) {
efree(EG(ht_iterators));
}
}
#if ZEND_DEBUG
if (EG(ht_iterators_used) && !CG(unclean_shutdown)) {
zend_error(E_WARNING, "Leaked %" PRIu32 " hashtable iterators", EG(ht_iterators_used));
}
@@ -1187,64 +1212,78 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string *
}
return NULL;
}
/* Verify class name before passing it to the autoloader. */
if (!key && !ZSTR_HAS_CE_CACHE(name) && !zend_is_valid_class_name(name)) {
zend_string_release_ex(lc_name, 0);
return NULL;
}
- if (EG(in_autoload) == NULL) {
- ALLOC_HASHTABLE(EG(in_autoload));
- zend_hash_init(EG(in_autoload), 8, NULL, NULL, 0);
- }
-
- if (zend_hash_add_empty_element(EG(in_autoload), lc_name) == NULL) {
+ if (zend_set_in_autoload(lc_name) != SUCCESS) {
+ /* Already autoloading class; bail to avoid recursing forever */
if (!key) {
zend_string_release_ex(lc_name, 0);
}
return NULL;
}
if (ZSTR_VAL(name)[0] == '\\') {
autoload_name = zend_string_init(ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1, 0);
} else {
autoload_name = zend_string_copy(name);
}
zend_exception_save();
ce = zend_autoload(autoload_name, lc_name);
zend_exception_restore();
zend_string_release_ex(autoload_name, 0);
- zend_hash_del(EG(in_autoload), lc_name);
+ zend_clear_in_autoload(lc_name);
if (!key) {
zend_string_release_ex(lc_name, 0);
}
if (ce) {
ZEND_ASSERT(!CG(in_compilation));
if (ce_cache) {
SET_CE_CACHE(ce_cache, ce);
}
}
return ce;
}
/* }}} */
ZEND_API zend_class_entry *zend_lookup_class(zend_string *name) /* {{{ */
{
return zend_lookup_class_ex(name, NULL, 0);
}
/* }}} */
+ZEND_API uint32_t zend_in_autoload_count(void) /* {{{ */
+{
+ if (!EG(in_autoload)) {
+ return 0;
+ }
+ return zend_hash_num_elements(EG(in_autoload));
+}
+/* }}} */
+
+ZEND_API bool zend_is_in_autoload(zend_string *lc_name) /* {{{ */
+{
+ if (!EG(in_autoload)) {
+ return false;
+ }
+ return zend_hash_exists(EG(in_autoload), lc_name);
+}
+/* }}} */
+
ZEND_API zend_class_entry *zend_get_called_scope(zend_execute_data *ex) /* {{{ */
{
while (ex) {
if (Z_TYPE(ex->This) == IS_OBJECT) {
return Z_OBJCE(ex->This);
} else if (Z_CE(ex->This)) {
return Z_CE(ex->This);
} else if (ex->func) {
if (ex->func->type != ZEND_INTERNAL_FUNCTION || ex->func->common.scope) {
return NULL;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment