diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index ee83ee75ff6ea..585a798c55fb8 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -9686,6 +9686,20 @@ static void zend_compile_class_decl(znode *result, const zend_ast *ast, bool top } } + /* When compiling without execution (opcache_compile_file), link simple + * classes so opcache can early-bind them from cache. Skip preloading. */ + if (!ce->num_interfaces && !ce->num_traits && !ce->num_hooked_prop_variance_checks +#ifdef ZEND_OPCACHE_SHM_REATTACHMENT + && !ce->num_hooked_props +#endif + && !extends_ast + && (CG(compiler_options) & ZEND_COMPILE_WITHOUT_EXECUTION) + && !(CG(compiler_options) & ZEND_COMPILE_PRELOAD)) { + zend_build_properties_info_table(ce); + zend_inheritance_check_override(ce); + ce->ce_flags |= ZEND_ACC_LINKED; + } + opline = get_next_op(); if (ce->parent_name) { diff --git a/ext/opcache/tests/gh18714.phpt b/ext/opcache/tests/gh18714.phpt new file mode 100644 index 0000000000000..b79afa8c09986 --- /dev/null +++ b/ext/opcache/tests/gh18714.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-18714 (opcache_compile_file() breaks class hoisting) +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +--FILE-- + +--CLEAN-- + +--EXPECT-- +HelloWorld diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index 1eaa183f9df5b..02e07b39f3510 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -984,6 +984,11 @@ ZEND_FUNCTION(opcache_compile_file) orig_compiler_options = CG(compiler_options); CG(compiler_options) |= ZEND_COMPILE_WITHOUT_EXECUTION; + /* Save class/function table state so we can undo the side effects + * of zend_accel_load_script() called by persistent_compile_file(). */ + uint32_t orig_class_count = EG(class_table)->nNumUsed; + uint32_t orig_function_count = EG(function_table)->nNumUsed; + if (CG(compiler_options) & ZEND_COMPILE_PRELOAD) { /* During preloading, a failure in opcache_compile_file() should result in an overall * preloading failure. Otherwise we may include partially compiled files in the preload @@ -1001,6 +1006,14 @@ ZEND_FUNCTION(opcache_compile_file) CG(compiler_options) = orig_compiler_options; if(op_array != NULL) { + /* Undo classes/functions registered by zend_accel_load_script(). + * opcache_compile_file() should only cache without side effects. + * Skip during preloading: preload needs the registrations to persist. */ + if (!(orig_compiler_options & ZEND_COMPILE_PRELOAD)) { + zend_hash_discard(EG(class_table), orig_class_count); + zend_hash_discard(EG(function_table), orig_function_count); + } + destroy_op_array(op_array); efree(op_array); RETVAL_TRUE;