Skip to content

Commit 6686068

Browse files
authored
Zend: move class autoloading from SPL to Zend (#21001)
The primary motivation for this change is that this sort of functionality should reside in core and not in an extension. The reason being is that this causes issues in regard to extension dependencies and resolution, something that prevents GH-14544.
1 parent cc50c9e commit 6686068

7 files changed

Lines changed: 220 additions & 124 deletions

File tree

Zend/zend_autoload.c

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Zend Engine |
4+
+----------------------------------------------------------------------+
5+
| Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
6+
+----------------------------------------------------------------------+
7+
| This source file is subject to version 2.00 of the Zend license, |
8+
| that is bundled with this package in the file LICENSE, and is |
9+
| available through the world-wide-web at the following url: |
10+
| http://www.zend.com/license/2_00.txt. |
11+
| If you did not receive a copy of the Zend license and are unable to |
12+
| obtain it through the world-wide-web, please send a note to |
13+
| license@zend.com so we can mail you a copy immediately. |
14+
+----------------------------------------------------------------------+
15+
| Authors: Gina Peter Banyard <girgias@php.net> |
16+
+----------------------------------------------------------------------+
17+
*/
18+
19+
#include "zend.h"
20+
#include "zend_API.h"
21+
#include "zend_autoload.h"
22+
#include "zend_hash.h"
23+
#include "zend_types.h"
24+
#include "zend_exceptions.h"
25+
#include "zend_string.h"
26+
27+
ZEND_TLS HashTable *zend_class_autoload_functions;
28+
29+
static void zend_autoload_callback_zval_destroy(zval *element)
30+
{
31+
zend_fcall_info_cache *fcc = Z_PTR_P(element);
32+
zend_fcc_dtor(fcc);
33+
efree(fcc);
34+
}
35+
36+
static Bucket *autoload_find_registered_function(const HashTable *autoloader_table, const zend_fcall_info_cache *function_entry)
37+
{
38+
zend_fcall_info_cache *current_function_entry;
39+
ZEND_HASH_MAP_FOREACH_PTR(autoloader_table, current_function_entry) {
40+
if (zend_fcc_equals(current_function_entry, function_entry)) {
41+
return _p;
42+
}
43+
} ZEND_HASH_FOREACH_END();
44+
return NULL;
45+
}
46+
47+
ZEND_API zend_class_entry *zend_perform_class_autoload(zend_string *class_name, zend_string *lc_name)
48+
{
49+
if (!zend_class_autoload_functions) {
50+
return NULL;
51+
}
52+
53+
zval zname;
54+
ZVAL_STR(&zname, class_name);
55+
56+
const HashTable *class_autoload_functions = zend_class_autoload_functions;
57+
58+
/* Cannot use ZEND_HASH_MAP_FOREACH_PTR here as autoloaders may be
59+
* added/removed during autoloading. */
60+
HashPosition pos;
61+
zend_hash_internal_pointer_reset_ex(class_autoload_functions, &pos);
62+
while (true) {
63+
zend_fcall_info_cache *func_info = zend_hash_get_current_data_ptr_ex(class_autoload_functions, &pos);
64+
if (!func_info) {
65+
break;
66+
}
67+
zend_call_known_fcc(func_info, /* retval */ NULL, /* param_count */ 1, /* params */ &zname, /* named_params */ NULL);
68+
69+
if (EG(exception)) {
70+
return NULL;
71+
}
72+
if (ZSTR_HAS_CE_CACHE(class_name) && ZSTR_GET_CE_CACHE(class_name)) {
73+
return (zend_class_entry*)ZSTR_GET_CE_CACHE(class_name);
74+
}
75+
76+
zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), lc_name);
77+
if (ce) {
78+
return ce;
79+
}
80+
81+
zend_hash_move_forward_ex(class_autoload_functions, &pos);
82+
}
83+
return NULL;
84+
}
85+
86+
/* Needed for compatibility with spl_register_autoload() */
87+
ZEND_API void zend_autoload_register_class_loader(zend_fcall_info_cache *fcc, bool prepend)
88+
{
89+
ZEND_ASSERT(ZEND_FCC_INITIALIZED(*fcc));
90+
91+
if (!zend_class_autoload_functions) {
92+
ALLOC_HASHTABLE(zend_class_autoload_functions);
93+
zend_hash_init(zend_class_autoload_functions, 1, NULL, zend_autoload_callback_zval_destroy, false);
94+
/* Initialize as non-packed hash table for prepend functionality. */
95+
zend_hash_real_init_mixed(zend_class_autoload_functions);
96+
}
97+
98+
ZEND_ASSERT(
99+
fcc->function_handler->type != ZEND_INTERNAL_FUNCTION
100+
|| !zend_string_equals_literal(fcc->function_handler->common.function_name, "spl_autoload_call")
101+
);
102+
103+
/* If function is already registered, don't do anything */
104+
if (autoload_find_registered_function(zend_class_autoload_functions, fcc)) {
105+
/* Release potential call trampoline */
106+
zend_release_fcall_info_cache(fcc);
107+
return;
108+
}
109+
110+
zend_fcc_addref(fcc);
111+
zend_hash_next_index_insert_mem(zend_class_autoload_functions, fcc, sizeof(zend_fcall_info_cache));
112+
if (prepend && zend_hash_num_elements(zend_class_autoload_functions) > 1) {
113+
/* Move the newly created element to the head of the hashtable */
114+
ZEND_ASSERT(!HT_IS_PACKED(zend_class_autoload_functions));
115+
Bucket tmp = zend_class_autoload_functions->arData[zend_class_autoload_functions->nNumUsed-1];
116+
memmove(zend_class_autoload_functions->arData + 1, zend_class_autoload_functions->arData, sizeof(Bucket) * (zend_class_autoload_functions->nNumUsed - 1));
117+
zend_class_autoload_functions->arData[0] = tmp;
118+
zend_hash_rehash(zend_class_autoload_functions);
119+
}
120+
}
121+
122+
ZEND_API bool zend_autoload_unregister_class_loader(const zend_fcall_info_cache *fcc) {
123+
if (zend_class_autoload_functions) {
124+
Bucket *p = autoload_find_registered_function(zend_class_autoload_functions, fcc);
125+
if (p) {
126+
zend_hash_del_bucket(zend_class_autoload_functions, p);
127+
return true;
128+
}
129+
}
130+
return false;
131+
}
132+
133+
/* We do not return a HashTable* because zend_empty_array is not collectable,
134+
* therefore the zval holding this value must do so. Something that ZVAL_EMPTY_ARRAY(); does. */
135+
ZEND_API void zend_autoload_fcc_map_to_callable_zval_map(zval *return_value) {
136+
if (zend_class_autoload_functions) {
137+
zend_fcall_info_cache *fcc;
138+
139+
zend_array *map = zend_new_array(zend_hash_num_elements(zend_class_autoload_functions));
140+
ZEND_HASH_MAP_FOREACH_PTR(zend_class_autoload_functions, fcc) {
141+
zval tmp;
142+
zend_get_callable_zval_from_fcc(fcc, &tmp);
143+
zend_hash_next_index_insert(map, &tmp);
144+
} ZEND_HASH_FOREACH_END();
145+
RETURN_ARR(map);
146+
}
147+
RETURN_EMPTY_ARRAY();
148+
}
149+
150+
/* Only for deprecated strange behaviour of spl_autoload_unregister() */
151+
ZEND_API void zend_autoload_clean_class_loaders(void)
152+
{
153+
if (zend_class_autoload_functions) {
154+
/* Don't destroy the hash table, as we might be iterating over it right now. */
155+
zend_hash_clean(zend_class_autoload_functions);
156+
}
157+
}
158+
159+
void zend_autoload_shutdown(void)
160+
{
161+
if (zend_class_autoload_functions) {
162+
zend_hash_destroy(zend_class_autoload_functions);
163+
FREE_HASHTABLE(zend_class_autoload_functions);
164+
zend_class_autoload_functions = NULL;
165+
}
166+
}

Zend/zend_autoload.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Zend Engine |
4+
+----------------------------------------------------------------------+
5+
| Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
6+
+----------------------------------------------------------------------+
7+
| This source file is subject to version 2.00 of the Zend license, |
8+
| that is bundled with this package in the file LICENSE, and is |
9+
| available through the world-wide-web at the following url: |
10+
| http://www.zend.com/license/2_00.txt. |
11+
| If you did not receive a copy of the Zend license and are unable to |
12+
| obtain it through the world-wide-web, please send a note to |
13+
| license@zend.com so we can mail you a copy immediately. |
14+
+----------------------------------------------------------------------+
15+
| Authors: Gina Peter Banyard <girgias@php.net> |
16+
+----------------------------------------------------------------------+
17+
*/
18+
19+
#include "zend_string.h"
20+
#include "zend_hash.h"
21+
#include "zend_API.h"
22+
#include "zend.h"
23+
24+
ZEND_API zend_class_entry *zend_perform_class_autoload(zend_string *class_name, zend_string *lc_name);
25+
ZEND_API void zend_autoload_register_class_loader(zend_fcall_info_cache *fcc, bool prepend);
26+
ZEND_API bool zend_autoload_unregister_class_loader(const zend_fcall_info_cache *fcc);
27+
ZEND_API void zend_autoload_fcc_map_to_callable_zval_map(zval *return_value);
28+
/* Only for deprecated strange behaviour of spl_autoload_unregister() */
29+
ZEND_API void zend_autoload_clean_class_loaders(void);
30+
void zend_autoload_shutdown(void);

Zend/zend_builtin_functions.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@
2929
#include "zend_extensions.h"
3030
#include "zend_closures.h"
3131
#include "zend_generators.h"
32+
#include "zend_autoload.h"
3233
#include "zend_builtin_functions_arginfo.h"
3334
#include "zend_smart_str.h"
3435

3536
/* }}} */
3637

3738
ZEND_MINIT_FUNCTION(core) { /* {{{ */
39+
zend_autoload = zend_perform_class_autoload;
3840
zend_register_default_classes();
3941

4042
zend_standard_class_def = register_class_stdClass();

configure.ac

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1738,6 +1738,7 @@ PHP_ADD_SOURCES([Zend], m4_normalize([
17381738
zend_ast.c
17391739
zend_atomic.c
17401740
zend_attributes.c
1741+
zend_autoload.c
17411742
zend_builtin_functions.c
17421743
zend_call_stack.c
17431744
zend_closures.c

0 commit comments

Comments
 (0)