Skip to content

Preload unlinked classes, remove preload autoload #7311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ ZEND_API char *(*zend_getenv)(const char *name, size_t name_len);
ZEND_API zend_string *(*zend_resolve_path)(zend_string *filename);
ZEND_API zend_result (*zend_post_startup_cb)(void) = NULL;
ZEND_API void (*zend_post_shutdown_cb)(void) = NULL;
ZEND_API zend_result (*zend_preload_autoload)(zend_string *filename) = NULL;

/* This callback must be signal handler safe! */
void (*zend_on_timeout)(int seconds);
Expand Down
3 changes: 0 additions & 3 deletions Zend/zend.h
Original file line number Diff line number Diff line change
Expand Up @@ -332,9 +332,6 @@ extern ZEND_API zend_string *(*zend_resolve_path)(zend_string *filename);
extern ZEND_API zend_result (*zend_post_startup_cb)(void);
extern ZEND_API void (*zend_post_shutdown_cb)(void);

/* Callback for loading of not preloaded part of the script */
extern ZEND_API zend_result (*zend_preload_autoload)(zend_string *filename);

ZEND_API ZEND_COLD void zend_error(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);
ZEND_API ZEND_COLD ZEND_NORETURN void zend_error_noreturn(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);
/* For custom format specifiers like H */
Expand Down
43 changes: 20 additions & 23 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1104,9 +1104,16 @@ ZEND_API zend_class_entry *zend_bind_class_in_slot(
zval *class_table_slot, zval *lcname, zend_string *lc_parent_name)
{
zend_class_entry *ce = Z_PTR_P(class_table_slot);
zval *zv = zend_hash_set_bucket_key(
EG(class_table), (Bucket *) class_table_slot, Z_STR_P(lcname));
if (UNEXPECTED(!zv)) {
bool is_preloaded =
(ce->ce_flags & ZEND_ACC_PRELOADED) && !(CG(compiler_options) & ZEND_COMPILE_PRELOAD);
bool success;
if (EXPECTED(!is_preloaded)) {
success = zend_hash_set_bucket_key(EG(class_table), (Bucket*) class_table_slot, Z_STR_P(lcname)) != NULL;
} else {
/* If preloading is used, don't replace the existing bucket, add a new one. */
success = zend_hash_add_ptr(EG(class_table), Z_STR_P(lcname), ce) != NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this is necessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the original bucket (< persistent_classes_count) is replaced, it won't get cleaned up on shutdown, and we'll leak a class from the previous request into the next one.

}
if (UNEXPECTED(!success)) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name));
return NULL;
}
Expand All @@ -1120,9 +1127,13 @@ ZEND_API zend_class_entry *zend_bind_class_in_slot(
return ce;
}

/* Reload bucket pointer, the hash table may have been reallocated */
zv = zend_hash_find(EG(class_table), Z_STR_P(lcname));
zend_hash_set_bucket_key(EG(class_table), (Bucket *) zv, Z_STR_P(lcname + 1));
if (!is_preloaded) {
/* Reload bucket pointer, the hash table may have been reallocated */
zval *zv = zend_hash_find(EG(class_table), Z_STR_P(lcname));
zend_hash_set_bucket_key(EG(class_table), (Bucket *) zv, Z_STR_P(lcname + 1));
} else {
zend_hash_del(EG(class_table), Z_STR_P(lcname));
}
return NULL;
}

Expand All @@ -1137,23 +1148,9 @@ ZEND_API zend_result do_bind_class(zval *lcname, zend_string *lc_parent_name) /*

if (UNEXPECTED(!zv)) {
ce = zend_hash_find_ptr(EG(class_table), Z_STR_P(lcname));
if (ce) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name));
return FAILURE;
} else {
do {
ZEND_ASSERT(EG(current_execute_data)->func->op_array.fn_flags & ZEND_ACC_PRELOADED);
if (zend_preload_autoload
&& zend_preload_autoload(EG(current_execute_data)->func->op_array.filename) == SUCCESS) {
zv = zend_hash_find_known_hash(EG(class_table), Z_STR_P(rtd_key));
if (EXPECTED(zv != NULL)) {
break;
}
}
zend_error_noreturn(E_ERROR, "Class %s wasn't preloaded", Z_STRVAL_P(lcname));
return FAILURE;
} while (0);
}
ZEND_ASSERT(ce);
zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name));
return FAILURE;
}

/* Register the derived class */
Expand Down
17 changes: 12 additions & 5 deletions Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -3017,12 +3017,19 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e

static zend_always_inline bool register_early_bound_ce(zval *delayed_early_binding, zend_string *lcname, zend_class_entry *ce) {
if (delayed_early_binding) {
if (UNEXPECTED(zend_hash_set_bucket_key(EG(class_table), (Bucket *)delayed_early_binding, lcname) == NULL)) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name));
return false;
if (EXPECTED(!(ce->ce_flags & ZEND_ACC_PRELOADED))) {
if (zend_hash_set_bucket_key(EG(class_table), (Bucket *)delayed_early_binding, lcname) != NULL) {
Z_CE_P(delayed_early_binding) = ce;
return true;
}
} else {
/* If preloading is used, don't replace the existing bucket, add a new one. */
if (zend_hash_add_ptr(EG(class_table), lcname, ce) != NULL) {
return true;
}
}
Z_CE_P(delayed_early_binding) = ce;
return true;
zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name));
return false;
}
return zend_hash_add_ptr(CG(class_table), lcname, ce) != NULL;
}
Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_opcode.c
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ ZEND_API void destroy_zend_class(zval *zv)
zend_class_entry *ce = Z_PTR_P(zv);
zend_function *fn;

if (ce->ce_flags & (ZEND_ACC_IMMUTABLE|ZEND_ACC_PRELOADED)) {
if (ce->ce_flags & ZEND_ACC_IMMUTABLE) {
return;
}

Expand Down
14 changes: 0 additions & 14 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -7609,20 +7609,6 @@ ZEND_VM_HANDLER(146, ZEND_DECLARE_ANON_CLASS, ANY, ANY, CACHE_SLOT)
if (UNEXPECTED(ce == NULL)) {
zend_string *rtd_key = Z_STR_P(RT_CONSTANT(opline, opline->op1));
zv = zend_hash_find_known_hash(EG(class_table), rtd_key);
if (UNEXPECTED(zv == NULL)) {
SAVE_OPLINE();
do {
ZEND_ASSERT(EX(func)->op_array.fn_flags & ZEND_ACC_PRELOADED);
if (zend_preload_autoload
&& zend_preload_autoload(EX(func)->op_array.filename) == SUCCESS) {
zv = zend_hash_find_known_hash(EG(class_table), rtd_key);
if (EXPECTED(zv != NULL)) {
break;
}
}
zend_error_noreturn(E_ERROR, "Anonymous class wasn't preloaded");
} while (0);
}
ZEND_ASSERT(zv != NULL);
ce = Z_CE_P(zv);
if (!(ce->ce_flags & ZEND_ACC_LINKED)) {
Expand Down
14 changes: 0 additions & 14 deletions Zend/zend_vm_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -2937,20 +2937,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DECLARE_ANON_CLASS_SPEC_HANDLE
if (UNEXPECTED(ce == NULL)) {
zend_string *rtd_key = Z_STR_P(RT_CONSTANT(opline, opline->op1));
zv = zend_hash_find_known_hash(EG(class_table), rtd_key);
if (UNEXPECTED(zv == NULL)) {
SAVE_OPLINE();
do {
ZEND_ASSERT(EX(func)->op_array.fn_flags & ZEND_ACC_PRELOADED);
if (zend_preload_autoload
&& zend_preload_autoload(EX(func)->op_array.filename) == SUCCESS) {
zv = zend_hash_find_known_hash(EG(class_table), rtd_key);
if (EXPECTED(zv != NULL)) {
break;
}
}
zend_error_noreturn(E_ERROR, "Anonymous class wasn't preloaded");
} while (0);
}
ZEND_ASSERT(zv != NULL);
ce = Z_CE_P(zv);
if (!(ce->ce_flags & ZEND_ACC_LINKED)) {
Expand Down
98 changes: 2 additions & 96 deletions ext/opcache/ZendAccelerator.c
Original file line number Diff line number Diff line change
Expand Up @@ -3911,8 +3911,6 @@ static void preload_link(void)
zend_string *key;
bool found, changed;
uint32_t i;
dtor_func_t orig_dtor;
zend_function *function;

/* Resolve class dependencies */
do {
Expand Down Expand Up @@ -4040,9 +4038,7 @@ static void preload_link(void)
} ZEND_HASH_FOREACH_END();
} while (changed);

/* Move unlinked clases (and with unresolved constants) back to scripts */
orig_dtor = EG(class_table)->pDestructor;
EG(class_table)->pDestructor = NULL;
/* Warn for classes that could not be linked. */
ZEND_HASH_REVERSE_FOREACH_STR_KEY_VAL(EG(class_table), key, zv) {
ce = Z_PTR_P(zv);
if (ce->type == ZEND_INTERNAL_CLASS) {
Expand All @@ -4064,25 +4060,8 @@ static void preload_link(void)
ZSTR_VAL(ce->name), kind, name);
}
zend_string_release(key);
} else {
continue;
}
ce->ce_flags &= ~ZEND_ACC_PRELOADED;
ZEND_HASH_FOREACH_PTR(&ce->function_table, function) {
if (EXPECTED(function->type == ZEND_USER_FUNCTION)
&& function->common.scope == ce) {
function->common.fn_flags &= ~ZEND_ACC_PRELOADED;
}
} ZEND_HASH_FOREACH_END();
script = zend_hash_find_ptr(preload_scripts, ce->info.user.filename);
ZEND_ASSERT(script);
zend_hash_add(&script->script.class_table, key, zv);
ZVAL_UNDEF(zv);
zend_string_release(key);
EG(class_table)->nNumOfElements--;
} ZEND_HASH_FOREACH_END();
EG(class_table)->pDestructor = orig_dtor;
zend_hash_rehash(EG(class_table));

/* Remove DECLARE opcodes */
ZEND_HASH_FOREACH_PTR(preload_scripts, script) {
Expand All @@ -4096,7 +4075,7 @@ static void preload_link(void)
case ZEND_DECLARE_CLASS:
case ZEND_DECLARE_CLASS_DELAYED:
key = Z_STR_P(RT_CONSTANT(opline, opline->op1) + 1);
if (!zend_hash_exists(&script->script.class_table, key)) {
if (!zend_hash_exists(CG(class_table), key)) {
MAKE_NOP(opline);
}
break;
Expand Down Expand Up @@ -4414,8 +4393,6 @@ static zend_persistent_script* preload_script_in_shared_memory(zend_persistent_s
return new_persistent_script;
}

static zend_result preload_autoload(zend_string *filename);

static void preload_load(void)
{
/* Load into process tables */
Expand Down Expand Up @@ -4459,77 +4436,6 @@ static void preload_load(void)
memset((void **) ZEND_MAP_PTR_REAL_BASE(CG(map_ptr_base)) + old_map_ptr_last, 0,
(CG(map_ptr_last) - old_map_ptr_last) * sizeof(void *));
}

zend_preload_autoload = preload_autoload;
}

static zend_result preload_autoload(zend_string *filename)
{
zend_persistent_script *persistent_script;
zend_op_array *op_array;
zend_execute_data *old_execute_data;
zend_class_entry *old_fake_scope;
bool do_bailout = 0;
int ret = SUCCESS;

if (zend_hash_exists(&EG(included_files), filename)) {
return FAILURE;
}

persistent_script = zend_accel_hash_find(&ZCSG(hash), filename);
if (!persistent_script) {
return FAILURE;
}

zend_hash_add_empty_element(&EG(included_files), filename);

if (persistent_script->ping_auto_globals_mask & ~ZCG(auto_globals_mask)) {
zend_accel_set_auto_globals(persistent_script->ping_auto_globals_mask & ~ZCG(auto_globals_mask));
}

op_array = zend_accel_load_script(persistent_script, 1);
if (!op_array) {
return FAILURE;
}

/* Execute in global context */
old_execute_data = EG(current_execute_data);
EG(current_execute_data) = NULL;
old_fake_scope = EG(fake_scope);
EG(fake_scope) = NULL;
zend_exception_save();

zend_try {
zend_execute(op_array, NULL);
} zend_catch {
do_bailout = 1;
} zend_end_try();

if (EG(exception)) {
ret = FAILURE;
}

zend_exception_restore();
EG(fake_scope) = old_fake_scope;
EG(current_execute_data) = old_execute_data;
while (old_execute_data) {
if (old_execute_data->func && (ZEND_CALL_INFO(old_execute_data) & ZEND_CALL_HAS_SYMBOL_TABLE)) {
if (old_execute_data->symbol_table == &EG(symbol_table)) {
zend_attach_symbol_table(old_execute_data);
}
break;
}
old_execute_data = old_execute_data->prev_execute_data;
}

destroy_op_array(op_array);
efree_size(op_array, sizeof(zend_op_array));

if (do_bailout) {
zend_bailout();
}

return ret;
}

static int accel_preload(const char *config, bool in_child)
Expand Down