diff --git a/.gitignore b/.gitignore index d54334f599a57..d14903c61157d 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,7 @@ Makefile.objects # Directories for shared object files generated by `./configure` libs/ modules/ +!Zend/tests/modules/ # Used by build/gen_stub.php build/PHP-Parser-* diff --git a/Zend/tests/modules/check_deps_001.phpt b/Zend/tests/modules/check_deps_001.phpt new file mode 100644 index 0000000000000..66cd3485b613f --- /dev/null +++ b/Zend/tests/modules/check_deps_001.phpt @@ -0,0 +1,13 @@ +--TEST-- +Modules: classes that are never declared are not checked +--ENV-- +THIS_IS_DEFINED=1 +--FILE-- + +==DONE== +--EXPECT-- +==DONE== diff --git a/Zend/tests/modules/check_deps_001/a.inc b/Zend/tests/modules/check_deps_001/a.inc new file mode 100644 index 0000000000000..35fa19a6775f0 --- /dev/null +++ b/Zend/tests/modules/check_deps_001/a.inc @@ -0,0 +1,45 @@ +getMessage()); +} + +?> +==DONE== +--EXPECTF-- +Error: Class Test\I not found while compiling module Test%s +==DONE== diff --git a/Zend/tests/modules/check_deps_002/a.inc b/Zend/tests/modules/check_deps_002/a.inc new file mode 100644 index 0000000000000..d9d1c4c428b0f --- /dev/null +++ b/Zend/tests/modules/check_deps_002/a.inc @@ -0,0 +1,5 @@ +getMessage()); +} + +?> +==DONE== +--EXPECTF-- +Error: Class Test\I not found while compiling module Test%s +==DONE== diff --git a/Zend/tests/modules/check_deps_003/a.inc b/Zend/tests/modules/check_deps_003/a.inc new file mode 100644 index 0000000000000..4b8e768d07aae --- /dev/null +++ b/Zend/tests/modules/check_deps_003/a.inc @@ -0,0 +1,7 @@ +getMessage()); +} + +?> +==DONE== +--EXPECTF-- +Error: Class Test\I not found while compiling module Test%s +==DONE== diff --git a/Zend/tests/modules/check_deps_004/a.inc b/Zend/tests/modules/check_deps_004/a.inc new file mode 100644 index 0000000000000..a4294542e29b7 --- /dev/null +++ b/Zend/tests/modules/check_deps_004/a.inc @@ -0,0 +1,11 @@ + +==DONE== +--EXPECT-- +==DONE== diff --git a/Zend/tests/modules/class_alias/m1/a.inc b/Zend/tests/modules/class_alias/m1/a.inc new file mode 100644 index 0000000000000..051bc8215a127 --- /dev/null +++ b/Zend/tests/modules/class_alias/m1/a.inc @@ -0,0 +1,3 @@ + +--EXPECT-- +Foo +Foo +Foo\C +Foo diff --git a/Zend/tests/modules/module_decl/a.inc b/Zend/tests/modules/module_decl/a.inc new file mode 100644 index 0000000000000..e9080145b16a1 --- /dev/null +++ b/Zend/tests/modules/module_decl/a.inc @@ -0,0 +1,11 @@ +getModuleName()); + diff --git a/Zend/tests/modules/module_decl/module.ini b/Zend/tests/modules/module_decl/module.ini new file mode 100644 index 0000000000000..df3dfd0e128ae --- /dev/null +++ b/Zend/tests/modules/module_decl/module.ini @@ -0,0 +1,2 @@ +module=Foo +files=*.inc diff --git a/Zend/tests/modules/module_decl_with_namespace.phpt b/Zend/tests/modules/module_decl_with_namespace.phpt new file mode 100644 index 0000000000000..b2cc275cf598c --- /dev/null +++ b/Zend/tests/modules/module_decl_with_namespace.phpt @@ -0,0 +1,13 @@ +--TEST-- +Module declaration with namespace +--FILE-- + +--EXPECT-- +Foo +Foo\Bar +Foo\Bar\C +Foo diff --git a/Zend/tests/modules/module_decl_with_namespace/a.inc b/Zend/tests/modules/module_decl_with_namespace/a.inc new file mode 100644 index 0000000000000..6db989a814e5f --- /dev/null +++ b/Zend/tests/modules/module_decl_with_namespace/a.inc @@ -0,0 +1,12 @@ +getModuleName()); + diff --git a/Zend/tests/modules/module_decl_with_namespace/module.ini b/Zend/tests/modules/module_decl_with_namespace/module.ini new file mode 100644 index 0000000000000..df3dfd0e128ae --- /dev/null +++ b/Zend/tests/modules/module_decl_with_namespace/module.ini @@ -0,0 +1,2 @@ +module=Foo +files=*.inc diff --git a/Zend/zend.c b/Zend/zend.c index b4a084b1f95c7..efc68359e0cae 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -55,17 +55,20 @@ ZEND_API size_t compiler_globals_offset; ZEND_API size_t executor_globals_offset; static HashTable *global_function_table = NULL; static HashTable *global_class_table = NULL; +static HashTable *global_module_table = NULL; static HashTable *global_constants_table = NULL; static HashTable *global_auto_globals_table = NULL; static HashTable *global_persistent_list = NULL; TSRMLS_MAIN_CACHE_DEFINE() # define GLOBAL_FUNCTION_TABLE global_function_table # define GLOBAL_CLASS_TABLE global_class_table +# define GLOBAL_MODULE_TABLE global_module_table # define GLOBAL_CONSTANTS_TABLE global_constants_table # define GLOBAL_AUTO_GLOBALS_TABLE global_auto_globals_table #else # define GLOBAL_FUNCTION_TABLE CG(function_table) # define GLOBAL_CLASS_TABLE CG(class_table) +# define GLOBAL_MODULE_TABLE CG(module_table) # define GLOBAL_AUTO_GLOBALS_TABLE CG(auto_globals) # define GLOBAL_CONSTANTS_TABLE EG(zend_constants) #endif @@ -723,6 +726,9 @@ static void compiler_globals_ctor(zend_compiler_globals *compiler_globals) /* {{ zend_hash_init(compiler_globals->class_table, 64, NULL, ZEND_CLASS_DTOR, 1); zend_hash_copy(compiler_globals->class_table, global_class_table, zend_class_add_ref); + compiler_globals->module_table = (HashTable *) malloc(sizeof(HashTable)); + zend_hash_init(compiler_globals->module_table, 64, NULL, NULL, 1); + zend_set_default_compile_time_values(); compiler_globals->auto_globals = (HashTable *) malloc(sizeof(HashTable)); @@ -752,6 +758,10 @@ static void compiler_globals_ctor(zend_compiler_globals *compiler_globals) /* {{ static void compiler_globals_dtor(zend_compiler_globals *compiler_globals) /* {{{ */ { + if (compiler_globals->module_table != GLOBAL_MODULE_TABLE) { + zend_hash_destroy(compiler_globals->module_table); + free(compiler_globals->module_table); + } if (compiler_globals->function_table != GLOBAL_FUNCTION_TABLE) { uint32_t n = compiler_globals->copied_functions_count; @@ -1005,11 +1015,13 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ GLOBAL_FUNCTION_TABLE = (HashTable *) malloc(sizeof(HashTable)); GLOBAL_CLASS_TABLE = (HashTable *) malloc(sizeof(HashTable)); + GLOBAL_MODULE_TABLE = (HashTable *) malloc(sizeof(HashTable)); GLOBAL_AUTO_GLOBALS_TABLE = (HashTable *) malloc(sizeof(HashTable)); GLOBAL_CONSTANTS_TABLE = (HashTable *) malloc(sizeof(HashTable)); zend_hash_init(GLOBAL_FUNCTION_TABLE, 1024, NULL, ZEND_FUNCTION_DTOR, 1); zend_hash_init(GLOBAL_CLASS_TABLE, 64, NULL, ZEND_CLASS_DTOR, 1); + zend_hash_init(GLOBAL_MODULE_TABLE, 64, NULL, NULL, 1); zend_hash_init(GLOBAL_AUTO_GLOBALS_TABLE, 8, NULL, auto_global_dtor, 1); zend_hash_init(GLOBAL_CONSTANTS_TABLE, 128, NULL, ZEND_CONSTANT_DTOR, 1); @@ -1028,9 +1040,11 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ compiler_globals->in_compilation = 0; compiler_globals->function_table = (HashTable *) malloc(sizeof(HashTable)); compiler_globals->class_table = (HashTable *) malloc(sizeof(HashTable)); + compiler_globals->module_table = (HashTable *) malloc(sizeof(HashTable)); *compiler_globals->function_table = *GLOBAL_FUNCTION_TABLE; *compiler_globals->class_table = *GLOBAL_CLASS_TABLE; + *compiler_globals->module_table = *GLOBAL_MODULE_TABLE; compiler_globals->auto_globals = GLOBAL_AUTO_GLOBALS_TABLE; zend_hash_destroy(executor_globals->zend_constants); @@ -1110,6 +1124,7 @@ zend_result zend_post_startup(void) /* {{{ */ #ifdef ZTS *GLOBAL_FUNCTION_TABLE = *compiler_globals->function_table; *GLOBAL_CLASS_TABLE = *compiler_globals->class_table; + *GLOBAL_MODULE_TABLE = *compiler_globals->module_table; *GLOBAL_CONSTANTS_TABLE = *executor_globals->zend_constants; global_map_ptr_last = compiler_globals->map_ptr_last; @@ -1121,6 +1136,8 @@ zend_result zend_post_startup(void) /* {{{ */ compiler_globals->function_table = NULL; free(compiler_globals->class_table); compiler_globals->class_table = NULL; + free(compiler_globals->module_table); + compiler_globals->module_table = NULL; if (compiler_globals->map_ptr_real_base) { free(compiler_globals->map_ptr_real_base); } diff --git a/Zend/zend.h b/Zend/zend.h index 0cf1faeb653fe..b1c90f2020599 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -225,6 +225,7 @@ struct _zend_class_entry { union { struct { + zend_string *user_module; zend_string *filename; uint32_t line_start; uint32_t line_end; diff --git a/Zend/zend_API.c b/Zend/zend_API.c index fd5b7c8db7966..b94a150ccdc8b 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3581,6 +3581,11 @@ ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_ ZVAL_ALIAS_PTR(&zv, ce); ret = zend_hash_add(CG(class_table), lcname, &zv); + + if (CG(active_module)) { + zend_hash_add(&CG(active_module)->class_table, lcname, &zv); + } + zend_string_release_ex(lcname, 0); if (ret) { // avoid notifying at MINIT time diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 6f68a961b7989..e0f7b075f84e9 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -144,6 +144,7 @@ enum _zend_ast_kind { ZEND_AST_USE_TRAIT, ZEND_AST_TRAIT_PRECEDENCE, ZEND_AST_METHOD_REFERENCE, + ZEND_AST_MODULE, ZEND_AST_NAMESPACE, ZEND_AST_USE_ELEM, ZEND_AST_TRAIT_ALIAS, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 88e61bba449b4..1bce24a996511 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -24,10 +24,12 @@ #include "zend_attributes.h" #include "zend_compile.h" #include "zend_constants.h" +#include "zend_errors.h" #include "zend_llist.h" #include "zend_API.h" #include "zend_exceptions.h" #include "zend_interfaces.h" +#include "zend_string.h" #include "zend_virtual_cwd.h" #include "zend_multibyte.h" #include "zend_language_scanner.h" @@ -386,6 +388,12 @@ static void zend_reset_import_tables(void) /* {{{ */ } /* }}} */ +static void zend_end_module(void) { + ZEND_ASSERT(!FC(in_namespace)); + FC(in_module) = 0; + FC(current_module) = NULL; +} + static void zend_end_namespace(void) /* {{{ */ { FC(in_namespace) = 0; zend_reset_import_tables(); @@ -413,6 +421,7 @@ void zend_file_context_begin(zend_file_context *prev_context) /* {{{ */ void zend_file_context_end(zend_file_context *prev_context) /* {{{ */ { zend_end_namespace(); + zend_end_module(); zend_hash_destroy(&FC(seen_symbols)); CG(file_context) = *prev_context; } @@ -2159,6 +2168,7 @@ zend_ast *zend_negate_num_string(zend_ast *ast) /* {{{ */ static void zend_verify_namespace(void) /* {{{ */ { + // TODO: modules if (FC(has_bracketed_namespaces) && !FC(in_namespace)) { zend_error_noreturn(E_COMPILE_ERROR, "No code may exist outside of namespace {}"); } @@ -6824,7 +6834,7 @@ bool zend_handle_encoding_declaration(zend_ast *ast) /* {{{ */ } /* }}} */ -/* Check whether this is the first statement, not counting declares. */ +/* Check whether this is the first statement, not counting declares and modules. */ static zend_result zend_is_first_statement(zend_ast *ast, bool allow_nop) /* {{{ */ { uint32_t i = 0; @@ -6837,7 +6847,8 @@ static zend_result zend_is_first_statement(zend_ast *ast, bool allow_nop) /* {{{ if (!allow_nop) { return FAILURE; } - } else if (file_ast->child[i]->kind != ZEND_AST_DECLARE) { + } else if (file_ast->child[i]->kind != ZEND_AST_DECLARE + && file_ast->child[i]->kind != ZEND_AST_MODULE) { return FAILURE; } i++; @@ -8269,6 +8280,11 @@ static zend_op_array *zend_compile_func_decl_ex( op_array->fn_flags |= (orig_op_array->fn_flags & ZEND_ACC_STRICT_TYPES); op_array->fn_flags |= decl->flags; + if (CG(active_module)) { + op_array->user_module = zend_string_copy(CG(active_module)->desc.lcname); + } else { + op_array->user_module = NULL; + } op_array->line_start = decl->start_lineno; op_array->line_end = decl->end_lineno; if (decl->doc_comment) { @@ -9074,6 +9090,11 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) } ce->ce_flags |= decl->flags; + if (CG(active_module)) { + ce->info.user.user_module = zend_string_copy(CG(active_module)->desc.lcname); + } else { + ce->info.user.user_module = NULL; + } ce->info.user.filename = zend_string_copy(zend_get_compiled_filename()); ce->info.user.line_start = decl->start_lineno; ce->info.user.line_end = decl->end_lineno; @@ -9469,6 +9490,53 @@ static void zend_compile_const_decl(zend_ast *ast) /* {{{ */ } /* }}}*/ +static void zend_compile_module(zend_ast *ast) +{ + zend_ast *name_ast = ast->child[0]; + zend_ast *stmt_ast = ast->child[1]; + zend_string *name; + + // TODO: bracketed modules? + if (stmt_ast) { + zend_error_noreturn(E_COMPILE_ERROR, "Bracketed modules not supported yet"); + } + + if (FC(current_module)) { + zend_error_noreturn(E_COMPILE_ERROR, "Can not re-declare module"); + } + + if (FC(current_namespace)) { + zend_error_noreturn(E_COMPILE_ERROR, "Can not declare module after namespace"); + } + + // TODO: empty module name? + if (!name_ast) { + zend_error_noreturn(E_COMPILE_ERROR, "Unnamed modules not supported yet"); + } + + name = zend_ast_get_str(name_ast); + + // TODO: may not be the right place for this + if (!CG(active_module)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Can not declare module outside of require_module()"); + } else if (!zend_string_equals_ci(CG(active_module)->desc.name, name)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Can not declare module '%s' while loading module '%s'", + ZSTR_VAL(name), ZSTR_VAL(CG(active_module)->desc.name)); + } + + CG(active_op_array)->user_module = zend_string_copy(CG(active_module)->desc.lcname); + + FC(current_module) = CG(active_module); + FC(current_namespace) = zend_string_copy(CG(active_module)->desc.name); + + zend_reset_import_tables(); + + FC(in_module) = 1; + FC(in_namespace) = 1; +} + static void zend_compile_namespace(zend_ast *ast) /* {{{ */ { zend_ast *name_ast = ast->child[0]; @@ -9495,7 +9563,7 @@ static void zend_compile_namespace(zend_ast *ast) /* {{{ */ } } - bool is_first_namespace = (!with_bracket && !FC(current_namespace)) + bool is_first_namespace = (!with_bracket && (!FC(current_namespace) || FC(current_namespace) == FC(current_module)->desc.name)) || (with_bracket && !FC(has_bracketed_namespaces)); if (is_first_namespace && FAILURE == zend_is_first_statement(ast, /* allow_nop */ 1)) { zend_error_noreturn(E_COMPILE_ERROR, "Namespace declaration statement has to be " @@ -9513,7 +9581,13 @@ static void zend_compile_namespace(zend_ast *ast) /* {{{ */ zend_error_noreturn(E_COMPILE_ERROR, "Cannot use '%s' as namespace name", ZSTR_VAL(name)); } - FC(current_namespace) = zend_string_copy(name); + if (FC(current_module)) { + FC(current_namespace) = zend_string_concat3( + ZSTR_VAL(FC(current_module)->desc.name), ZSTR_LEN(FC(current_module)->desc.name), + "\\", 1, ZSTR_VAL(name), ZSTR_LEN(name)); + } else { + FC(current_namespace) = zend_string_copy(name); + } } else { FC(current_namespace) = NULL; } @@ -9647,6 +9721,13 @@ static bool zend_try_ct_eval_magic_const(zval *zv, zend_ast *ast) /* {{{ */ ZVAL_EMPTY_STRING(zv); } break; + case T_MODULE_C: + if (FC(current_module)) { + ZVAL_STR_COPY(zv, FC(current_module)->desc.name); + } else { + ZVAL_EMPTY_STRING(zv); + } + break; EMPTY_SWITCH_DEFAULT_CASE() } @@ -11338,7 +11419,9 @@ void zend_compile_top_stmt(zend_ast *ast) /* {{{ */ } else { zend_compile_stmt(ast); } - if (ast->kind != ZEND_AST_NAMESPACE && ast->kind != ZEND_AST_HALT_COMPILER) { + if (ast->kind != ZEND_AST_MODULE + && ast->kind != ZEND_AST_NAMESPACE + && ast->kind != ZEND_AST_HALT_COMPILER) { zend_verify_namespace(); } } @@ -11437,6 +11520,9 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ case ZEND_AST_CONST_DECL: zend_compile_const_decl(ast); break; + case ZEND_AST_MODULE: + zend_compile_module(ast); + break; case ZEND_AST_NAMESPACE: zend_compile_namespace(ast); break; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 1eaf3ef686e79..e0423c1051f67 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -51,6 +51,7 @@ } \ } while (0) +typedef struct _zend_user_module zend_user_module; typedef struct _zend_op_array zend_op_array; typedef struct _zend_op zend_op; @@ -110,6 +111,9 @@ typedef struct _zend_declarables { typedef struct _zend_file_context { zend_declarables declarables; + zend_user_module *current_module; + bool in_module; + zend_string *current_namespace; bool in_namespace; bool has_bracketed_namespaces; @@ -534,6 +538,7 @@ struct _zend_op_array { zend_live_range *live_range; zend_try_catch_element *try_catch_array; + zend_string *user_module; zend_string *filename; uint32_t line_start; uint32_t line_end; @@ -549,6 +554,35 @@ struct _zend_op_array { void *reserved[ZEND_MAX_RESERVED_RESOURCES]; }; +typedef struct _zend_user_module_desc { + zend_string *desc_path; + // TODO +#ifdef ZEND_WIN32 + unsigned __int64 timestamp; +#else + time_t timestamp; +#endif + zend_string *name; + zend_string *lcname; + zend_string *root; /* ends with DEFAULT_SLASH_STR */ + zend_string **include_patterns; + size_t num_include_patterns; + zend_string **exclude_patterns; + size_t num_exclude_patterns; +} zend_user_module_desc; + +struct _zend_user_module { + zend_user_module_desc desc; + bool is_loading; + bool is_persistent; + HashTable deps; + HashTable class_table; + HashTable function_table; + // TODO: move these elsewhere + HashTable scripts; + HashTable classmap; + struct _zend_user_module_dir_cache *dir_cache; +}; #define ZEND_RETURN_VALUE 0 #define ZEND_RETURN_REFERENCE 1 diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 9ebc15f3a43fb..27b6cc39fadc7 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -25,6 +25,7 @@ #include "zend_compile.h" #include "zend_execute.h" #include "zend_API.h" +#include "zend_hash.h" #include "zend_stack.h" #include "zend_constants.h" #include "zend_extensions.h" @@ -39,6 +40,7 @@ #include "zend_observer.h" #include "zend_call_stack.h" #include "zend_frameless_function.h" +#include "zend_smart_str.h" #ifdef HAVE_SYS_TIME_H #include #endif @@ -146,6 +148,7 @@ void init_executor(void) /* {{{ */ EG(function_table) = CG(function_table); EG(class_table) = CG(class_table); + EG(module_table) = CG(module_table); EG(in_autoload) = NULL; EG(error_handling) = EH_NORMAL; @@ -460,6 +463,7 @@ void shutdown_executor(void) /* {{{ */ */ zend_hash_discard(EG(function_table), EG(persistent_functions_count)); zend_hash_discard(EG(class_table), EG(persistent_classes_count)); + zend_hash_clean(EG(module_table)); } else { zend_vm_stack_destroy(); @@ -485,6 +489,8 @@ void shutdown_executor(void) /* {{{ */ } ZEND_HASH_MAP_FOREACH_END_DEL(); } + zend_hash_clean(EG(module_table)); + while (EG(symtable_cache_ptr) > EG(symtable_cache)) { EG(symtable_cache_ptr)--; zend_hash_destroy(*EG(symtable_cache_ptr)); @@ -1680,6 +1686,33 @@ static ZEND_COLD void report_class_fetch_error(zend_string *class_name, uint32_t return; } +#if 0 + if (CG(active_module)) { + /* We are loading a module. Check if the class is part of a module being + * loaded. */ + zend_user_module *module; + ZEND_HASH_FOREACH_PTR(EG(module_table), module) { + if (zend_string_starts_with_ci(class_name, module->desc.name) + // FIXME: this is wrong with nested modules: We need to find + // the longest match. + && ZSTR_VAL(class_name)[ZSTR_LEN(module->desc.name)] == '\\') { + if (module == CG(active_module) && module->is_loading) { + zend_throw_or_error(fetch_type, NULL, "Class %s not found while compiling module %s (possible cause: files with top-level statements may not be autoloaded.)", + ZSTR_VAL(class_name), ZSTR_VAL(module->desc.name)); + } else if (!module->is_loading) { + /* Module that would have declared class is fully loaded + * and did not declare this class: The class does not exist. + */ + break; + } else { + zend_circular_module_dependency_error(module); + } + return; + } + } ZEND_HASH_FOREACH_END(); + } +#endif + if (EG(exception)) { if (!(fetch_type & ZEND_FETCH_CLASS_EXCEPTION)) { zend_exception_uncaught_error("During class fetch"); diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 62a97d753634a..f46e17308888a 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -84,6 +84,8 @@ typedef enum { struct _zend_compiler_globals { zend_stack loop_var_stack; + zend_user_module *active_module; /* module being loaded */ + zend_class_entry *active_class_entry; zend_string *compiled_filename; @@ -94,6 +96,7 @@ struct _zend_compiler_globals { HashTable *function_table; /* function symbol table */ HashTable *class_table; /* class table */ + HashTable *module_table; HashTable *auto_globals; @@ -187,6 +190,7 @@ struct _zend_executor_globals { HashTable *function_table; /* function symbol table */ HashTable *class_table; /* class table */ HashTable *zend_constants; /* constants table */ + HashTable *module_table; zval *vm_stack_top; zval *vm_stack_end; diff --git a/Zend/zend_hash.h b/Zend/zend_hash.h index d543dae2bab59..7bd8ec8dc0670 100644 --- a/Zend/zend_hash.h +++ b/Zend/zend_hash.h @@ -1328,6 +1328,10 @@ static zend_always_inline void *zend_hash_get_current_data_ptr_ex(HashTable *ht, ZEND_HASH_MAP_FOREACH(ht, 0); \ _ptr = Z_PTR_P(_z); +#define ZEND_HASH_MAP_FOREACH_VAL_FROM(ht, _val, _from) \ + ZEND_HASH_MAP_FOREACH_FROM(ht, 0, _from); \ + _val = _z; + #define ZEND_HASH_MAP_FOREACH_PTR_FROM(ht, _ptr, _from) \ ZEND_HASH_MAP_FOREACH_FROM(ht, 0, _from); \ _ptr = Z_PTR_P(_z); @@ -1382,6 +1386,10 @@ static zend_always_inline void *zend_hash_get_current_data_ptr_ex(HashTable *ht, _key = _p->key; \ _val = _z; +#define ZEND_HASH_MAP_FOREACH_STR_KEY_FROM(ht, _key, _from) \ + ZEND_HASH_MAP_FOREACH_FROM(ht, 0, _from); \ + _key = _p->key; \ + #define ZEND_HASH_MAP_REVERSE_FOREACH_STR_KEY_VAL(ht, _key, _val) \ ZEND_HASH_MAP_REVERSE_FOREACH(ht, 0); \ _key = _p->key; \ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index d85bfeb6ae2ab..40799f4147b75 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -3513,7 +3513,8 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string #ifndef ZEND_WIN32 if (ce->ce_flags & ZEND_ACC_ENUM) { /* We will add internal methods. */ - is_cacheable = false; + // TODO + // is_cacheable = false; } #endif diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index d2a29e670d8bf..2ec9ea7d5bd1b 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -169,6 +169,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_ENUM "'enum'" %token T_EXTENDS "'extends'" %token T_IMPLEMENTS "'implements'" +%token T_MODULE "'module'" %token T_NAMESPACE "'namespace'" %token T_LIST "'list'" %token T_ARRAY "'array'" @@ -182,6 +183,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_FUNC_C "'__FUNCTION__'" %token T_PROPERTY_C "'__PROPERTY__'" %token T_NS_C "'__NAMESPACE__'" +%token T_MODULE_C "'__MODULE__'" +// TODO: __MODULE__ %token END 0 "end of file" %token T_ATTRIBUTE "'#['" @@ -279,7 +282,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type identifier type_expr_without_static union_type_without_static_element union_type_without_static intersection_type_without_static %type inline_function union_type_element union_type intersection_type %type attributed_statement attributed_class_statement attributed_parameter -%type attribute_decl attribute attributes attribute_group namespace_declaration_name +%type attribute_decl attribute attributes attribute_group module_declaration_name namespace_declaration_name %type match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list %type enum_declaration_statement enum_backing_type enum_case enum_case_expr %type function_name non_empty_member_modifiers @@ -309,7 +312,7 @@ reserved_non_modifiers: | T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT | T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS | T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_FN | T_MATCH | T_ENUM - | T_PROPERTY_C + | T_PROPERTY_C | T_MODULE_C ; semi_reserved: @@ -336,6 +339,12 @@ top_statement_list: | %empty { $$ = zend_ast_create_list(0, ZEND_AST_STMT_LIST); } ; +/* Name usable in a module declaration. */ +module_declaration_name: + identifier { $$ = $1; } + | T_NAME_QUALIFIED { $$ = $1; } +; + /* Name usable in a namespace declaration. */ namespace_declaration_name: identifier { $$ = $1; } @@ -400,6 +409,9 @@ top_statement: { $$ = zend_ast_create(ZEND_AST_HALT_COMPILER, zend_ast_create_zval_from_long(zend_get_scanned_file_offset())); zend_stop_lexing(); } + | T_MODULE module_declaration_name ';' + { $$ = zend_ast_create(ZEND_AST_MODULE, $2, NULL); + RESET_DOC_COMMENT(); } | T_NAMESPACE namespace_declaration_name ';' { $$ = zend_ast_create(ZEND_AST_NAMESPACE, $2, NULL); RESET_DOC_COMMENT(); } @@ -1459,6 +1471,7 @@ constant: | T_METHOD_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_METHOD_C); } | T_FUNC_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_FUNC_C); } | T_PROPERTY_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_PROPERTY_C); } + | T_MODULE_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_MODULE_C); } | T_NS_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_NS_C); } | T_CLASS_C { $$ = zend_ast_create_ex(ZEND_AST_MAGIC_CONST, T_CLASS_C); } ; diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 7ae73875926eb..1f0504803be9b 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -591,6 +591,7 @@ static zend_op_array *zend_compile(int type) { zend_op_array *op_array = NULL; bool original_in_compilation = CG(in_compilation); + uint32_t original_compiler_options = CG(compiler_options); CG(in_compilation) = 1; CG(ast) = NULL; @@ -625,12 +626,26 @@ static zend_op_array *zend_compile(int type) zend_file_context_end(&original_file_context); CG(active_op_array) = original_active_op_array; + +#if 0 + if (CG(active_module) && op_array->user_module) { + ZEND_ASSERT(op_array->user_module == CG(active_module)->name); + if (op_array->refcount) { + (*op_array->refcount)++; + } + zend_op_array *new_op_array = (zend_op_array *) emalloc(sizeof(zend_op_array)); + *new_op_array = *op_array; + zend_hash_update_ptr(&CG(active_module)->op_arrays, + op_array->filename, new_op_array); + } +#endif } zend_ast_destroy(CG(ast)); zend_arena_destroy(CG(ast_arena)); CG(in_compilation) = original_in_compilation; + CG(compiler_options) = original_compiler_options; return op_array; } @@ -1677,6 +1692,10 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_REQUIRE_ONCE); } +"module" { + RETURN_TOKEN_WITH_IDENT(T_MODULE); +} + "namespace" { RETURN_TOKEN_WITH_IDENT(T_NAMESPACE); } @@ -2220,6 +2239,10 @@ string: RETURN_TOKEN_WITH_IDENT(T_DIR); } +"__MODULE__" { + RETURN_TOKEN_WITH_IDENT(T_MODULE_C); +} + "__NAMESPACE__" { RETURN_TOKEN_WITH_IDENT(T_NS_C); } diff --git a/Zend/zend_observer.h b/Zend/zend_observer.h index bb8964692c370..ab7a2d8f77f1e 100644 --- a/Zend/zend_observer.h +++ b/Zend/zend_observer.h @@ -22,8 +22,11 @@ #define ZEND_OBSERVER_H #include "zend.h" +#include "zend_arena.h" #include "zend_compile.h" #include "zend_fibers.h" +#include "zend_map_ptr.h" +#include "zend_extensions.h" BEGIN_EXTERN_C() @@ -105,6 +108,14 @@ static zend_always_inline bool zend_observer_fcall_has_no_observers(zend_execute return true; } + /* Internal function added at runtime and cached + TODO: probably slow */ + if (!ZEND_MAP_PTR_GET(runtime_cache)) { + ZEND_MAP_PTR_SET(function->common.run_time_cache, + zend_arena_calloc(&CG(arena), 1, + zend_internal_run_time_cache_reserved_size())); + } + *handler = (zend_observer_fcall_begin_handler *)ZEND_MAP_PTR_GET(runtime_cache) + ZEND_OBSERVER_HANDLE(function); return zend_observer_handler_is_unobserved(*handler); } diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 0d1d8b6bf528f..010d9660ea106 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -61,6 +61,7 @@ void init_op_array(zend_op_array *op_array, uint8_t type, int initial_ops_size) op_array->vars = NULL; op_array->T = 0; + op_array->user_module = NULL; op_array->function_name = NULL; op_array->filename = zend_string_copy(zend_get_compiled_filename()); diff --git a/Zend/zend_string.h b/Zend/zend_string.h index 88263d5d7a5d3..db2b11967e291 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -171,9 +171,9 @@ static zend_always_inline uint32_t zend_string_delref(zend_string *s) return 1; } -static zend_always_inline zend_string *zend_string_alloc(size_t len, bool persistent) +static zend_always_inline zend_string *_zend_string_alloc(size_t len, bool persistent ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) { - zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent); + zend_string *ret = (zend_string *)pemalloc_rel(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent); GC_SET_REFCOUNT(ret, 1); GC_TYPE_INFO(ret) = GC_STRING | ((persistent ? IS_STR_PERSISTENT : 0) << GC_FLAGS_SHIFT); @@ -182,6 +182,8 @@ static zend_always_inline zend_string *zend_string_alloc(size_t len, bool persis return ret; } +#define zend_string_alloc(len, persistent) _zend_string_alloc(len, persistent ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) + static zend_always_inline zend_string *zend_string_safe_alloc(size_t n, size_t m, size_t l, bool persistent) { zend_string *ret = (zend_string *)safe_pemalloc(n, m, ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(l)), persistent); diff --git a/Zend/zend_virtual_cwd.h b/Zend/zend_virtual_cwd.h index 21735f6dfae57..3ec11f43baed9 100644 --- a/Zend/zend_virtual_cwd.h +++ b/Zend/zend_virtual_cwd.h @@ -73,6 +73,7 @@ typedef unsigned short mode_t; #define DEFAULT_SLASH '\\' +#define DEFAULT_SLASH_STR "\\" #define DEFAULT_DIR_SEPARATOR ';' #define IS_SLASH(c) ((c) == '/' || (c) == '\\') // IS_SLASH_P() may read the previous char on Windows, which may be OOB; use IS_SLASH_P_EX() instead @@ -105,6 +106,7 @@ typedef unsigned short mode_t; #endif #define DEFAULT_SLASH '/' +#define DEFAULT_SLASH_STR "/" #ifdef __riscos__ #define DEFAULT_DIR_SEPARATOR ';' diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 3bb69eb107e35..5b1ebe9ec66b1 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -31,6 +31,7 @@ #include "zend_accelerator_blacklist.h" #include "zend_list.h" #include "zend_execute.h" +#include "zend_string.h" #include "zend_vm.h" #include "zend_inheritance.h" #include "zend_exceptions.h" @@ -128,7 +129,7 @@ static zend_op_array *(*accelerator_orig_compile_file)(zend_file_handle *file_ha static zend_class_entry* (*accelerator_orig_inheritance_cache_get)(zend_class_entry *ce, zend_class_entry *parent, zend_class_entry **traits_and_interfaces); static zend_class_entry* (*accelerator_orig_inheritance_cache_add)(zend_class_entry *ce, zend_class_entry *proto, zend_class_entry *parent, zend_class_entry **traits_and_interfaces, HashTable *dependencies); static zend_result (*accelerator_orig_zend_stream_open_function)(zend_file_handle *handle ); -static zend_string *(*accelerator_orig_zend_resolve_path)(zend_string *filename); +zend_string *(*accelerator_orig_zend_resolve_path)(zend_string *filename); static zif_handler orig_chdir = NULL; static ZEND_INI_MH((*orig_include_path_on_modify)) = NULL; static zend_result (*orig_post_startup_cb)(void); @@ -1889,7 +1890,7 @@ static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handl return new_persistent_script; } -static zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int type) +static zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int type, zend_persistent_script **persistent_script_p) { zend_persistent_script *persistent_script; zend_op_array *op_array = NULL; @@ -1948,6 +1949,9 @@ static zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int zend_accel_set_auto_globals(persistent_script->ping_auto_globals_mask & ~ZCG(auto_globals_mask)); } + if (persistent_script_p) { + *persistent_script_p = persistent_script; + } return zend_accel_load_script(persistent_script, 1); } @@ -1956,6 +1960,9 @@ static zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int if (persistent_script) { from_memory = false; persistent_script = cache_script_in_file_cache(persistent_script, &from_memory); + if (persistent_script_p) { + *persistent_script_p = persistent_script; + } return zend_accel_load_script(persistent_script, from_memory); } @@ -1984,7 +1991,7 @@ static int check_persistent_script_access(zend_persistent_script *persistent_scr } /* zend_compile() replacement */ -zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) +zend_op_array *persistent_compile_file_ex(zend_file_handle *file_handle, int type, zend_persistent_script **persistent_script_p) { zend_persistent_script *persistent_script = NULL; zend_string *key = NULL; @@ -1997,16 +2004,16 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) if (file_handle->filename && ZCG(accel_directives).file_cache && ZCG(enabled) && accel_startup_ok) { - return file_cache_compile_file(file_handle, type); + return file_cache_compile_file(file_handle, type, persistent_script_p); } return accelerator_orig_compile_file(file_handle, type); } else if (file_cache_only) { ZCG(cache_opline) = NULL; ZCG(cache_persistent_script) = NULL; - return file_cache_compile_file(file_handle, type); + return file_cache_compile_file(file_handle, type, persistent_script_p); } else if ((ZCSG(restart_in_progress) && accel_restart_is_active())) { if (ZCG(accel_directives).file_cache) { - return file_cache_compile_file(file_handle, type); + return file_cache_compile_file(file_handle, type, persistent_script_p); } ZCG(cache_opline) = NULL; ZCG(cache_persistent_script) = NULL; @@ -2098,7 +2105,7 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) if (!ZCG(counted)) { if (accel_activate_add() == FAILURE) { if (ZCG(accel_directives).file_cache) { - return file_cache_compile_file(file_handle, type); + return file_cache_compile_file(file_handle, type, persistent_script_p); } return accelerator_orig_compile_file(file_handle, type); } @@ -2149,7 +2156,7 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) SHM_PROTECT(); HANDLE_UNBLOCK_INTERRUPTIONS(); if (ZCG(accel_directives).file_cache) { - return file_cache_compile_file(file_handle, type); + return file_cache_compile_file(file_handle, type, persistent_script_p); } return accelerator_orig_compile_file(file_handle, type); } @@ -2238,9 +2245,17 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) zend_accel_set_auto_globals(persistent_script->ping_auto_globals_mask & ~ZCG(auto_globals_mask)); } + if (persistent_script_p) { + *persistent_script_p = persistent_script; + } return zend_accel_load_script(persistent_script, from_shared_memory); } +zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) +{ + return persistent_compile_file_ex(file_handle, type, NULL); +} + static zend_always_inline zend_inheritance_cache_entry* zend_accel_inheritance_cache_find(zend_inheritance_cache_entry *entry, zend_class_entry *ce, zend_class_entry *parent, zend_class_entry **traits_and_interfaces, bool *needs_autoload_ptr) { uint32_t i; @@ -2760,12 +2775,12 @@ zend_result accel_activate(INIT_FUNC_ARGS) return SUCCESS; } -#ifdef HAVE_JIT void accel_deactivate(void) { +#ifdef HAVE_JIT zend_jit_deactivate(); -} #endif +} zend_result accel_post_deactivate(void) { @@ -4895,11 +4910,7 @@ ZEND_EXT_API zend_extension zend_extension_entry = { accel_startup, /* startup */ NULL, /* shutdown */ NULL, /* per-script activation */ -#ifdef HAVE_JIT accel_deactivate, /* per-script deactivation */ -#else - NULL, /* per-script deactivation */ -#endif NULL, /* message handler */ NULL, /* op_array handler */ NULL, /* extended statement handler */ diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index 3f7eb3bdf008f..5d28d0a2db71a 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -335,6 +335,8 @@ uint32_t zend_accel_get_class_name_map_ptr(zend_string *type_name); END_EXTERN_C() +extern zend_string *(*accelerator_orig_zend_resolve_path)(zend_string *filename); + /* memory write protection */ #define SHM_PROTECT() \ do { \ diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4 index 8f6d5ab711b28..cec71a03c2242 100644 --- a/ext/opcache/config.m4 +++ b/ext/opcache/config.m4 @@ -337,6 +337,7 @@ int main(void) { zend_persist.c zend_shared_alloc.c ZendAccelerator.c + modules.c $ZEND_JIT_SRC ]), [$ext_shared],, diff --git a/ext/opcache/modules.c b/ext/opcache/modules.c new file mode 100644 index 0000000000000..2aa800e894725 --- /dev/null +++ b/ext/opcache/modules.c @@ -0,0 +1,1690 @@ + +#include "modules.h" +#include "Zend/zend.h" +#include "zend_accelerator_hash.h" +#include "zend_alloc.h" +#include "zend_compile.h" +#include "zend_errors.h" +#include "zend_exceptions.h" +#include "zend_execute.h" +#include "zend_hash.h" +#include "zend_API.h" +#include "zend_ini.h" +#include "zend_ini_scanner.h" +#include "zend_operators.h" +#include "zend_portability.h" +#include "zend_smart_str.h" +#include "zend_string.h" +#include "zend_types.h" +#include "ZendAccelerator.h" +#include "zend_virtual_cwd.h" +#include "zend_vm_opcodes.h" +#include "zend_inheritance.h" +#include "zend_observer.h" +#include "zend_shared_alloc.h" +#include "zend_accelerator_util_funcs.h" +#include "zend_persist.h" +#include "zend_vm.h" + +#include +#include +#include +#include +#include +#include +#include + +ZEND_API zend_never_inline ZEND_COLD zend_string *zend_autoload_stack_str(void); + +#if ZTS +# error Not tested with ZTS +#endif + +#define ZUM_DEBUG_LEVEL ZEND_DEBUG + +#if ZUM_DEBUG_LEVEL +# define ZUM_DEBUG(...) do { fprintf(stderr, __VA_ARGS__); } while (0) +#else +# define ZUM_DEBUG(...) do { } while(0) +#endif +#if ZUM_DEBUG_LEVEL +# define ZUM_DEBUG_VALIDATION(...) do { fprintf(stderr, __VA_ARGS__); } while (0) +#else +# define ZUM_DEBUG_VALIDATION(...) do { } while(0) +#endif + +static void zum_desc_destroy(zend_user_module_desc *module_desc) +{ + if (module_desc->desc_path) { + zend_string_release(module_desc->desc_path); + } + if (module_desc->lcname) { + zend_string_release(module_desc->lcname); + } + if (module_desc->name) { + zend_string_release(module_desc->name); + } + if (module_desc->root) { + zend_string_release(module_desc->root); + } + if (module_desc->include_patterns) { + zend_string **str = module_desc->include_patterns; + zend_string **end = str + module_desc->num_include_patterns; + for ( ; str < end; str++) { + zend_string_release(*str); + } + efree(module_desc->include_patterns); + } + if (module_desc->exclude_patterns) { + zend_string **str = module_desc->exclude_patterns; + zend_string **end = str + module_desc->num_exclude_patterns; + for ( ; str < end; str++) { + zend_string_release(*str); + } + efree(module_desc->exclude_patterns); + } +} + +static void zum_optimize_exclude_pattern(zend_string *pattern) +{ + // "Test/*" -> "Test/" + if (ZSTR_VAL(pattern)[ZSTR_LEN(pattern)-1] == '*' + && ZSTR_VAL(pattern)[ZSTR_LEN(pattern)-2] == '/' + && memchr(ZSTR_VAL(pattern), '*', ZSTR_LEN(pattern)-1) == NULL) { + ZSTR_LEN(pattern) -= 1; + } +} + +static zend_string **zum_split_patterns(zend_string *str, size_t *num_patterns_p) +{ + size_t num_patterns = 0; + size_t capacity = 1; + zend_string **buf = emalloc(sizeof(zend_string*) * capacity); + + char *start = ZSTR_VAL(str); + char *end = start + ZSTR_LEN(str); + char *pos = start; + smart_str current = {0}; + + // TODO: comma separated? + // TODO: allow to escape separator + while (pos < end) { + switch (*pos) { + case ' ': + if (smart_str_get_len(¤t)) { + if (num_patterns == capacity) { + capacity = capacity * 2; + buf = erealloc(buf, sizeof(zend_string*) * capacity); + } + buf[num_patterns++] = smart_str_extract(¤t); + } + break; + default: + smart_str_appendc(¤t, *pos); + break; + } + pos++; + } + + if (smart_str_get_len(¤t)) { + if (num_patterns == capacity) { + capacity = capacity + 1; + buf = erealloc(buf, sizeof(zend_string*) * capacity); + } + buf[num_patterns++] = smart_str_extract(¤t); + } + + *num_patterns_p = num_patterns; + return buf; +} + +static void zum_validate_name(zend_string *name) +{ + // TODO: For now just check that there is no leading/trailing backslash + ZEND_ASSERT(ZSTR_LEN(name)); + if (ZSTR_VAL(name)[0] == '\\' || ZSTR_VAL(name)[ZSTR_LEN(name)]-1 == '\\') { + zend_error_noreturn(E_CORE_ERROR, + "Module name must not start or end with '\\': '%s'", + ZSTR_VAL(name)); + } +} + +static void zum_desc_parser_cb(zval *arg1, zval *arg2, zval *arg3, int callback_type, void *module_desc_ptr) +{ + zend_user_module_desc *module_desc = (zend_user_module_desc*) module_desc_ptr; + + switch (callback_type) { + case ZEND_INI_PARSER_ENTRY: + if (!arg2) { + /* no value given */; + break; + } + if (zend_string_equals_cstr(Z_STR_P(arg1), "module", strlen("module"))) { + if (module_desc->name) { + zend_error_noreturn(E_CORE_ERROR, "Duplicated 'module' entry"); + } + + convert_to_string(arg2); + zum_validate_name(Z_STR_P(arg2)); + module_desc->name = zend_string_copy(Z_STR_P(arg2)); + } else if (zend_string_equals_cstr(Z_STR_P(arg1), "files", strlen("files"))) { + if (module_desc->include_patterns) { + zend_error_noreturn(E_CORE_ERROR, "Duplicated 'files' entry"); + } + + convert_to_string(arg2); + module_desc->include_patterns = zum_split_patterns(Z_STR_P(arg2), &module_desc->num_include_patterns); + } else if (zend_string_equals_cstr(Z_STR_P(arg1), "exclude", strlen("exclude"))) { + if (module_desc->exclude_patterns) { + zend_error_noreturn(E_CORE_ERROR, "Duplicated 'exclude' entry"); + } + + convert_to_string(arg2); + module_desc->exclude_patterns = zum_split_patterns(Z_STR_P(arg2), &module_desc->num_exclude_patterns); + zend_string **p = module_desc->exclude_patterns; + zend_string **end = p + module_desc->num_exclude_patterns; + while (p < end) { + zum_optimize_exclude_pattern(*p); + p++; + } + } else { + zend_error_noreturn(E_CORE_ERROR, "Unknown entry: '%s'", Z_STRVAL_P(arg1)); + } + break; + + case ZEND_INI_PARSER_POP_ENTRY: + zend_error_noreturn(E_CORE_ERROR, "Unknown entry: '%s'", Z_STRVAL_P(arg1)); + break; + + default: + break; + } +} + +zend_op_array *persistent_compile_file_ex(zend_file_handle *file_handle, int type, zend_persistent_script **persistent_script_p); + +static zend_persistent_script *zum_compile_file(zend_user_module *module, zend_file_handle *file_handle) +{ + uint32_t num_included_files = EG(included_files).nNumUsed; + + zend_persistent_script *persistent_script = NULL; + zend_op_array *op_array = persistent_compile_file_ex(file_handle, ZEND_REQUIRE, &persistent_script); + + if (num_included_files != EG(included_files).nNumUsed) { + // persistent_compile_file() updates EG(included_files) in some cases + zend_hash_discard(&EG(included_files), num_included_files); + } + + if (!op_array) { + ZEND_ASSERT(EG(exception)); + return NULL; + } + destroy_op_array(op_array); + efree_size(op_array, sizeof(zend_op_array)); + + if (!persistent_script) { + /* For PoC purpose, we only support files that can be cached in SHM for + * now. */ + zend_error_noreturn(E_ERROR, "File %s could not be cached in opcache (SHM or file cache). This is unsupported in modules (yet).", + ZSTR_VAL(file_handle->filename)); + return NULL; + } + + op_array = &persistent_script->script.main_op_array; + + if (op_array->user_module) { + ZEND_ASSERT(zend_string_equals(op_array->user_module, module->desc.lcname)); + } else { + zend_error_noreturn(E_COMPILE_ERROR, + "Module file %s was expected to declare module %s, but did not declared a module", + ZSTR_VAL(file_handle->filename), ZSTR_VAL(module->desc.name)); + } + + return persistent_script; +} + +static zend_always_inline int zum_compare_filename_keys(Bucket *f, Bucket *s) +{ + size_t len = MIN(ZSTR_LEN(f->key), ZSTR_LEN(s->key)) + 1; + return memcmp(ZSTR_VAL(f->key), ZSTR_VAL(s->key), len); +} + +static zend_always_inline bool zum_file_excluded(zend_user_module_desc *module_desc, zend_string *filename) +{ + char *rel_filename = ZSTR_VAL(filename) + ZSTR_LEN(module_desc->root); + zend_string **exclude_pattern_p = module_desc->exclude_patterns; + if (!exclude_pattern_p) { + return false; + } + + zend_string **exclude_pattern_p_end = exclude_pattern_p + module_desc->num_exclude_patterns; + for (; exclude_pattern_p < exclude_pattern_p_end; exclude_pattern_p++) { + if (fnmatch(ZSTR_VAL(*exclude_pattern_p), rel_filename, 0) == 0) { + return true; + } + } + + return false; +} + +static zend_always_inline bool zum_file_matches(zend_user_module_desc *module_desc, zend_string *filename) +{ + ZEND_ASSERT(module_desc->include_patterns && module_desc->num_include_patterns); + ZEND_ASSERT(ZSTR_VAL(module_desc->root)[ZSTR_LEN(module_desc->root)-1] == DEFAULT_SLASH); + + char *rel_filename = ZSTR_VAL(filename) + ZSTR_LEN(module_desc->root); + + if (zum_file_excluded(module_desc, filename)) { + return false; + } + + zend_string **pattern_p = module_desc->include_patterns; + zend_string **pattern_p_end = pattern_p + module_desc->num_include_patterns; + for (; pattern_p < pattern_p_end; pattern_p++) { + if (fnmatch(ZSTR_VAL(*pattern_p), rel_filename, 0) == 0) { + return true; + } + } + + return false; +} + +/* dtor_func_t */ +static void zum_dir_cache_entry_dtor(zval *pDest) { + zend_user_module_dir_cache *dc = Z_PTR_P(pDest); + if (dc) { + zend_hash_destroy(&dc->entries); + efree(dc); + } +} + +#define ZUM_OPENDIR_FLAGS O_RDONLY|O_NDELAY|O_DIRECTORY|O_LARGEFILE|O_CLOEXEC + +static bool zum_validate_timestamps_fd(zend_user_module_desc *module_desc, + int dirfd, zend_user_module_dir_cache *cache) +{ + // TODO: We may want to update the cached directory timestamps, instead + // of invalidating the module, in case the directory was changed without + // affecting the list of files (e.g. an ignored file was added/removed). + + zend_string *name; + zval *zv; + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(&cache->entries, name, zv) { + if (ZUM_IS_FILE_ENTRY(zv)) { + accel_time_t mtime = ZUM_FILE_ENTRY_MTIME(zv); + + struct stat st; + if (fstatat(dirfd, ZSTR_VAL(name), &st, 0) != 0) { + ZUM_DEBUG("stat(%s) failed: %s\n", ZSTR_VAL(name), strerror(errno)); + return false; + } + + if (st.st_mtim.tv_sec != mtime) { + ZUM_DEBUG("Module file changed: %s\n", ZSTR_VAL(name)); + return false; + } + } else { + int fd = openat(dirfd, ZSTR_VAL(name), ZUM_OPENDIR_FLAGS); + if (fd == -1) { + ZUM_DEBUG("opendat(%s) failed: %s\n", ZSTR_VAL(name), strerror(errno)); + return false; + } + + struct stat st; + if (fstat(fd, &st) != 0) { + ZUM_DEBUG("stat(%s) failed: %s\n", ZSTR_VAL(name), strerror(errno)); + close(fd); + continue; + } + + if (st.st_mtim.tv_sec != ZUM_DIR_ENTRY(zv)->mtime) { + ZUM_DEBUG("Module dir changed: %s\n", ZSTR_VAL(name)); + close(fd); + return false; + } + + if (!zum_validate_timestamps_fd(module_desc, fd, ZUM_DIR_ENTRY(zv))) { + close(fd); + return false; + } + + close(fd); + } + } ZEND_HASH_FOREACH_END(); + + return true; +} + +static bool zum_validate_timestamps(zend_user_module_desc *module_desc, + zend_user_module_dir_cache *cache) +{ + int fd = open(ZSTR_VAL(module_desc->root), ZUM_OPENDIR_FLAGS); + if (fd == -1) { + ZUM_DEBUG("Failed opening module root directory: %s: %s\n", + ZSTR_VAL(module_desc->root), strerror(errno)); + return false; + } + + struct stat st; + if (fstat(fd, &st) != 0) { + ZUM_DEBUG("stat(%s) failed: %s\n", + ZSTR_VAL(module_desc->root), strerror(errno)); + close(fd); + return false; + } + + bool valid = zum_validate_timestamps_fd(module_desc, fd, cache); + close(fd); + + return valid; +} + +static zend_user_module_dir_cache *zum_dir_cache_new(accel_time_t mtime) +{ + zend_user_module_dir_cache *cache = emalloc(sizeof(zend_user_module_dir_cache)); + cache->mtime = mtime; + zend_hash_init(&cache->entries, 0, NULL, zum_dir_cache_entry_dtor, 0); + + return cache; +} + +/* bucket_compare_func_t: regular files, then directories, stable */ +static int zum_compare_dir_entries(Bucket *a, Bucket *b) +{ + int diff = ZUM_IS_FILE_ENTRY(&b->val) - ZUM_IS_FILE_ENTRY(&a->val); + if (diff != 0) { + return diff; + } + + return (uintptr_t)a - (uintptr_t)b; +} + +static void zum_collect_files(zend_user_module_desc *module_desc, + smart_str *root, + zend_user_module_dir_cache *cache, zend_array *filenames) +{ + ZEND_ASSERT(ZSTR_VAL(module_desc->root)[ZSTR_LEN(module_desc->root)-1] == DEFAULT_SLASH); + ZUM_DEBUG("Scanning %s\n", ZSTR_VAL(root->s)); + + size_t root_len = ZSTR_LEN(root->s); + + DIR *d = opendir(ZSTR_VAL(root->s)); + if (!d) { + ZUM_DEBUG("Failed opening %s: %s\n", ZSTR_VAL(root->s), strerror(errno)); + return; + } + + struct dirent *de; + while ((de = readdir(d))) { + if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || (de->d_name[1] == '.' && de->d_name[2] == '\0'))) { + continue; + } + + int fd = dirfd(d); + if (fd == -1) { + continue; + } + + struct stat st; + if (fstatat(fd, de->d_name, &st, 0) != 0) { + continue; + } + + bool is_dir; + if (S_ISDIR(st.st_mode)) { + is_dir = true; + } else if (S_ISREG(st.st_mode)) { + is_dir = false; + } else { + continue; + } + + ZSTR_LEN(root->s) = root_len; + smart_str_appends(root, de->d_name); + if (is_dir) { + smart_str_appendc(root, DEFAULT_SLASH); + } + smart_str_0(root); + + bool excluded = is_dir + ? zum_file_excluded(module_desc, root->s) + : !zum_file_matches(module_desc, root->s); + if (excluded) { + continue; + } + + zval entry; + if (is_dir) { + zend_user_module_dir_cache *new_cache = zum_dir_cache_new(st.st_mtim.tv_sec); + ZVAL_PTR(&entry, new_cache); + } else { + ZUM_FILE_ENTRY(&entry, st.st_mtim.tv_sec); + ZUM_DEBUG("Module file: %s\n", ZSTR_VAL(root->s)); + // Using str_add because we need to duplicate root->s + zend_hash_str_add(filenames, ZSTR_VAL(root->s), + ZSTR_LEN(root->s), &entry); + } + + zend_hash_str_add(&cache->entries, de->d_name, strlen(de->d_name), + &entry); + } + + zend_string *name; + zval *zv; + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(&cache->entries, name, zv) { + if (ZUM_IS_FILE_ENTRY(zv)) { + continue; + } + + ZSTR_LEN(root->s) = root_len; + smart_str_append(root, name); + smart_str_appendc(root, DEFAULT_SLASH); + smart_str_0(root); + + zum_collect_files(module_desc, root, ZUM_DIR_ENTRY(zv), filenames); + } ZEND_HASH_FOREACH_END(); + + // Move regular files first, directories last, so zum_validate_timestamps + // is breadth-first. + zend_hash_sort(&cache->entries, zum_compare_dir_entries, false); + + closedir(d); +} + +static void zum_find_files(zend_user_module_desc *module_desc, zend_user_module_dir_cache **cache_p, HashTable *filenames) +{ + /* Naive implementation of a recursive glob: + * - List root recursively + * - Filter with fnmatch() ('*' allows '/' in fnmatch, unlike glob) + */ + + if (!module_desc->num_include_patterns) { + ZUM_DEBUG("Module has no include patterns\n"); + return; + } + + smart_str root = {0}; + smart_str_append(&root, module_desc->root); + smart_str_0(&root); + + struct stat st; + if (stat(ZSTR_VAL(root.s), &st) != 0) { + return; + } + + zend_user_module_dir_cache *cache = zum_dir_cache_new(st.st_mtim.tv_sec); + *cache_p = cache; + + zum_collect_files(module_desc, &root, cache, filenames); + + smart_str_free(&root); + + zend_hash_sort(filenames, zum_compare_filename_keys, false); +} + +static bool zum_op_array_has_stmts(const zend_op_array *op_array) { + zend_op *opline = op_array->opcodes; + zend_op *end = opline + op_array->last; + + while (opline != end) { + switch (opline->opcode) { + case ZEND_DECLARE_CLASS: + case ZEND_DECLARE_CLASS_DELAYED: + case ZEND_DECLARE_FUNCTION: + case ZEND_RETURN: + break; + default: + return true; + } + opline++; + } + + return false; +} + +static zend_result zum_check_deps_class_name(zend_user_module *module, zend_string *name, zend_string *lcname); + +static zend_result zum_check_deps_type(zend_user_module *module, zend_type type) +{ + zend_type *single_type; + ZEND_TYPE_FOREACH(type, single_type) { + if (ZEND_TYPE_HAS_NAME(*single_type)) { + if (UNEXPECTED(zum_check_deps_class_name(module, ZEND_TYPE_NAME(*single_type), NULL) == FAILURE)) { + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + } else if (ZEND_TYPE_HAS_LIST(*single_type)) { + if (UNEXPECTED(zum_check_deps_type(module, *single_type) == FAILURE)) { + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + } + } ZEND_TYPE_FOREACH_END(); + + return SUCCESS; +} + +static zend_result zum_check_deps_op_array(zend_user_module *module, zend_op_array *op_array); + +static zend_result zum_check_deps_class(zend_user_module *module, zend_class_entry *ce) +{ + ZEND_ASSERT(!EG(exception)); + + if (ce->type != ZEND_USER_CLASS) { + return SUCCESS; + } + + if (UNEXPECTED(!ce->info.user.user_module)) { + zend_throw_error(NULL, + "Module %s can not depend on non-module class %s", + ZSTR_VAL(module->desc.name), ZSTR_VAL(ce->name)); + return FAILURE; + } + + if (!zend_string_equals(ce->info.user.user_module, module->desc.lcname)) { + ZUM_DEBUG("Module %s depends on %s\n", ZSTR_VAL(module->desc.lcname), ZSTR_VAL(ce->info.user.user_module)); + + if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE) && !ZCG(accel_directives).file_cache_only) { + zend_throw_error(NULL, + "Module %s can not depend on uncacheable class %s", + ZSTR_VAL(module->desc.name), ZSTR_VAL(ce->name)); + return FAILURE; + } + + zend_user_module *dep = zend_hash_find_ptr(CG(module_table), ce->info.user.user_module); + ZEND_ASSERT(dep); + zend_hash_add_ptr(&module->deps, ce->info.user.user_module, dep); + } + + return SUCCESS; +} + + +static zend_result zum_check_deps_class_decl_dep(zend_user_module *module, zend_class_entry *ce) +{ + if (ce->type != ZEND_USER_CLASS) { + return SUCCESS; + } + + return zum_check_deps_class(module, ce); +} + +static zend_result zum_check_deps_class_name(zend_user_module *module, zend_string *name, zend_string *lcname); + +static zend_result zum_check_deps_class_decl(zend_user_module *module, zend_class_entry *ce) +{ + ZEND_ASSERT(!EG(exception)); + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_LINKED); + + if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE) && !ZCG(accel_directives).file_cache_only) { + zend_throw_error(NULL, + "Class %s is uncacheable", + ZSTR_VAL(ce->name)); + return FAILURE; + } + + // TODO: may need to zum_check_deps_class_decl() for classes + // we don't visit through op_array walk, e.g. eval'ed classes. + if (ce->parent) { + if (zum_check_deps_class_decl_dep(module, ce->parent) == FAILURE) { + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + } + + for (uint32_t i = 0; i < ce->num_interfaces; i++) { + if (zum_check_deps_class_decl_dep(module, ce->interfaces[i]) == FAILURE) { + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + } + + for (uint32_t i = 0; i < ce->num_traits; i++) { + zend_class_name *trait_name = &ce->trait_names[i]; + if (zum_check_deps_class_name(module, trait_name->name, trait_name->lc_name) == FAILURE) { + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + } + + zend_property_info *prop_info; + ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, prop_info) { + if (UNEXPECTED(zum_check_deps_type(module, prop_info->type) == FAILURE)) { + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + // TODO: hooks + } ZEND_HASH_FOREACH_END(); + + zend_function *fn; + ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, fn) { + if (fn->type == ZEND_USER_FUNCTION && fn->op_array.scope == ce + && !(fn->common.fn_flags & ZEND_ACC_TRAIT_CLONE)) { + if (UNEXPECTED(zum_check_deps_op_array(module, &fn->op_array) == FAILURE)) { + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + } + } ZEND_HASH_FOREACH_END(); + + return SUCCESS; +} + +static zend_result zum_check_deps_class_name_ex(zend_user_module *module, zend_string *name, zend_string *lcname, uint32_t fetch_type) +{ + if (zend_string_equals_literal_ci(name, "self")) { + return SUCCESS; + } else if (zend_string_equals_literal_ci(name, "parent")) { + return SUCCESS; + } else if (zend_string_equals_ci(name, ZSTR_KNOWN(ZEND_STR_STATIC))) { + return SUCCESS; + } + + zend_class_entry *ce = zend_fetch_class_by_name(name, lcname, fetch_type); + if (UNEXPECTED(!ce)) { + if (fetch_type & ZEND_FETCH_CLASS_SILENT) { + return SUCCESS; + } + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + + return zum_check_deps_class(module, ce); +} + + +static zend_result zum_check_deps_class_name(zend_user_module *module, zend_string *name, zend_string *lcname) +{ + return zum_check_deps_class_name_ex(module, name, lcname, 0); +} + +static zend_result zum_check_deps_class_name_silent(zend_user_module *module, zend_string *name, zend_string *lcname) +{ + return zum_check_deps_class_name_ex(module, name, lcname, ZEND_FETCH_CLASS_SILENT); +} + +#define CHECK_CLASS(node) \ + do { \ + uint32_t ___lineno = EG(lineno_override); \ + EG(lineno_override) = opline->lineno; \ + zum_check_deps_class_name(module, Z_STR_P(RT_CONSTANT(opline, node)), Z_STR_P(RT_CONSTANT(opline, node)+1)); \ + EG(lineno_override) = ___lineno; \ + if (EG(exception)) { \ + return FAILURE; \ + } \ + } while (0) + +/* Relax restrictions for now, for class references that do not impact linking */ +#define CHECK_CLASS_SILENT(node) \ + do { \ + uint32_t ___lineno = EG(lineno_override); \ + EG(lineno_override) = opline->lineno; \ + zum_check_deps_class_name_silent(module, Z_STR_P(RT_CONSTANT(opline, node)), Z_STR_P(RT_CONSTANT(opline, node)+1)); \ + EG(lineno_override) = ___lineno; \ + if (EG(exception)) { \ + return FAILURE; \ + } \ + } while (0) + +static zend_result zum_check_deps_op_array(zend_user_module *module, zend_op_array *op_array) +{ + if (op_array->arg_info) { + zend_arg_info *start = op_array->arg_info + - (bool)(op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE); + zend_arg_info *end = op_array->arg_info + + op_array->num_args + + (bool)(op_array->fn_flags & ZEND_ACC_VARIADIC); + while (start < end) { + zum_check_deps_type(module, start->type); + start++; + } + } + + zend_op *opline = op_array->opcodes; + zend_op *end = opline + op_array->last; + + while (opline < end) { + switch (opline->opcode) { + case ZEND_INIT_STATIC_METHOD_CALL: + if (opline->op1_type == IS_CONST) { + CHECK_CLASS_SILENT(opline->op1); + } + break; + case ZEND_CATCH: + CHECK_CLASS_SILENT(opline->op1); + break; + case ZEND_FETCH_CLASS_CONSTANT: + if (opline->op1_type == IS_CONST) { + CHECK_CLASS_SILENT(opline->op1); + } + break; + case ZEND_ASSIGN_STATIC_PROP: + case ZEND_ASSIGN_STATIC_PROP_REF: + case ZEND_FETCH_STATIC_PROP_R: + case ZEND_FETCH_STATIC_PROP_W: + case ZEND_FETCH_STATIC_PROP_RW: + case ZEND_FETCH_STATIC_PROP_IS: + case ZEND_FETCH_STATIC_PROP_UNSET: + case ZEND_FETCH_STATIC_PROP_FUNC_ARG: + case ZEND_UNSET_STATIC_PROP: + case ZEND_ISSET_ISEMPTY_STATIC_PROP: + case ZEND_PRE_INC_STATIC_PROP: + case ZEND_PRE_DEC_STATIC_PROP: + case ZEND_POST_INC_STATIC_PROP: + case ZEND_POST_DEC_STATIC_PROP: + case ZEND_ASSIGN_STATIC_PROP_OP: + if (opline->op2_type == IS_CONST) { + CHECK_CLASS_SILENT(opline->op2); + } + break; + case ZEND_INSTANCEOF: + break; + case ZEND_FETCH_CLASS: + if (opline->op2_type == IS_CONST) { + CHECK_CLASS_SILENT(opline->op2); + } + break; + case ZEND_NEW: + if (opline->op1_type == IS_CONST) { + CHECK_CLASS_SILENT(opline->op1); + } + break; + case ZEND_DECLARE_CLASS: + case ZEND_DECLARE_CLASS_DELAYED: { + if (op_array->function_name) { + zend_error_noreturn(E_COMPILE_ERROR, "Can not declare named class in function body"); + } + zend_string *lcname = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + // ZUM_DEBUG("declare_class: %s\n", lcname->val); + zend_string *rtd_key = Z_STR_P(RT_CONSTANT(opline, opline->op1) + 1); + if (zend_hash_find_ptr(CG(class_table), rtd_key)) { + // This declaration was not executed. + // + // We need to check both rtd_key and lcname for cases like + // this: + // class C {} + // alias(C,D) + // if(false) { class D{} } + break; + } + zend_class_entry *ce = zend_hash_find_ptr(CG(class_table), lcname); + if (ce) { + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_LINKED); + if (UNEXPECTED(zum_check_deps_class_decl(module, ce) == FAILURE)) { + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + zend_hash_add_new_ptr(&module->class_table, lcname, ce); + // ce->refcount++; + } + break; + } + case ZEND_DECLARE_ANON_CLASS: { + zend_string *rtd_key = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + zend_class_entry *ce = zend_hash_find_ptr(CG(class_table), rtd_key); + ZEND_ASSERT(ce); + if (!(ce->ce_flags & ZEND_ACC_LINKED)) { + EG(filename_override) = ce->info.user.filename; + EG(lineno_override) = ce->info.user.line_start; + ce = zend_do_link_class(ce, (opline->op2_type == IS_CONST) ? Z_STR_P(RT_CONSTANT(opline, opline->op2)) : NULL, rtd_key); + if (UNEXPECTED(!ce)) { + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_LINKED); + } + if (UNEXPECTED(zum_check_deps_class_decl(module, ce) == FAILURE)) { + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + if (zend_hash_add_ptr(&module->class_table, rtd_key, ce)) { + // ce->refcount++; + } + break; + } + case ZEND_DECLARE_FUNCTION: { + zend_string *lcname = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + zend_function *fn = (zend_function*) op_array->dynamic_func_defs[opline->op2.num]; + ZEND_ASSERT(fn->type == ZEND_USER_FUNCTION); + if (zend_hash_add_ptr(&module->function_table, lcname, fn)) { + if (fn->op_array.refcount) { + // (*fn->op_array.refcount)++; + } + if (UNEXPECTED(zum_check_deps_op_array(module, &fn->op_array) == FAILURE)) { + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + } + break; + } + } + opline++; + } + + if (op_array->num_dynamic_func_defs) { + for (uint32_t i = 0; i < op_array->num_dynamic_func_defs; i++) { + if (UNEXPECTED(zum_check_deps_op_array(module, op_array->dynamic_func_defs[i]) == FAILURE)) { + ZEND_ASSERT(EG(exception)); + return FAILURE; + } + } + } + + return SUCCESS; +} + +/* ZEND_HASH_MAP_FOREACH_FROM, but append-safe and iterate on newly added elements */ +#define ZEND_HASH_MAP_FOREACH_FROM_APPEND(_ht, indirect, _from) do { \ + const HashTable *__ht = (_ht); \ + ZEND_ASSERT(!HT_IS_PACKED(__ht)); \ + for (uint32_t _i = (_from); _i < __ht->nNumUsed; _i++) { \ + Bucket *_p = __ht->arData + _i; \ + zval *_z = &_p->val; \ + if (indirect && Z_TYPE_P(_z) == IS_INDIRECT) { \ + _z = Z_INDIRECT_P(_z); \ + } \ + if (UNEXPECTED(Z_TYPE_P(_z) == IS_UNDEF)) continue; \ + +/* Check that every referenced symbol exists (or try to autoload it). + * Also build the class_table / function_table of the module. + * + * Referenced symbols include parent classes, interfaces, type hints. + * + * It is important that we ignore classes and functions that are defined but + * never declared, so that this does not error when Dependency does not exist: + * if (class_exists(Dependency::class)) { + * class ... extends Dependency { + * } + * } + */ +static zend_result zum_check_deps(zend_user_module *module, + uint32_t class_table_offset, uint32_t function_table_offset) +{ + ZEND_ASSERT(!EG(exception)); + + zend_string *filename_override = EG(filename_override); + zend_long lineno_override = EG(lineno_override); + ZEND_HASH_MAP_FOREACH_FROM_APPEND(&module->scripts, 0, 0) { + zend_persistent_script *persistent_script = Z_PTR_P(_z); + EG(filename_override) = persistent_script->script.filename; + EG(lineno_override) = 0; + zend_string *key; + zend_class_entry *ce; + if (UNEXPECTED(zum_check_deps_op_array(module, &persistent_script->script.main_op_array) == FAILURE)) { + ZEND_ASSERT(EG(exception)); + goto failure; + } + // Opcache may elide some DECLARE opcodes, so we have to check + // persistent_script->script.class_table as well. + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&persistent_script->script.class_table, key, ce) { + // Runtime declaration key: This is used by a DECLARE_CLASS opcode, + // so we already handled this in + // zum_check_deps_op_array(). + if (ZSTR_VAL(key)[0] == 0) { + continue; + } + // Anon class: This is used by a DECLARE_ANON_CLASS, so we already + // handled this in zum_check_deps_op_array(). Also, + // checking anon classes here may check classes that are not + // actually usable (e.g. declared in a class that were never + // declared at runtime). + if (ce->ce_flags & ZEND_ACC_ANON_CLASS) { + continue; + } + if (!(ce->ce_flags & ZEND_ACC_LINKED)) { + EG(filename_override) = ce->info.user.filename; + EG(lineno_override) = ce->info.user.line_start; + ce = zend_do_link_class(ce, NULL, key); + if (!ce) { + ZEND_ASSERT(EG(exception)); + goto failure; + } + } + if (zend_hash_add_ptr(&module->class_table, key, ce)) { + if (UNEXPECTED(zum_check_deps_class_decl(module, ce) == FAILURE)) { + ZEND_ASSERT(EG(exception)); + goto failure; + } + } + } ZEND_HASH_FOREACH_END(); + zend_function *function; + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&persistent_script->script.function_table, key, function) { + if (UNEXPECTED(zum_check_deps_op_array(module, &function->op_array) == FAILURE)) { + ZEND_ASSERT(EG(exception)); + goto failure; + } + + ZEND_ASSERT(function->type == ZEND_USER_FUNCTION); + zend_hash_add_ptr(&module->function_table, key, function); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); + + EG(filename_override) = filename_override; + EG(lineno_override) = lineno_override; + return SUCCESS; + +failure: + EG(filename_override) = filename_override; + EG(lineno_override) = lineno_override; + ZEND_ASSERT(EG(exception)); + return FAILURE; +} + +/** + * Clear AVX/SSE2-aligned memory. + */ +static void bzero_aligned(void *mem, size_t size) +{ +#if defined(__x86_64__) + memset(mem, 0, size); +#elif defined(__AVX__) + char *p = (char*)mem; + char *end = p + size; + __m256i ymm0 = _mm256_setzero_si256(); + + while (p < end) { + _mm256_store_si256((__m256i*)p, ymm0); + _mm256_store_si256((__m256i*)(p+32), ymm0); + p += 64; + } +#elif defined(__SSE2__) + char *p = (char*)mem; + char *end = p + size; + __m128i xmm0 = _mm_setzero_si128(); + + while (p < end) { + _mm_store_si128((__m128i*)p, xmm0); + _mm_store_si128((__m128i*)(p+16), xmm0); + _mm_store_si128((__m128i*)(p+32), xmm0); + _mm_store_si128((__m128i*)(p+48), xmm0); + p += 64; + } +#else + memset(mem, 0, size); +#endif +} + +static zend_persistent_user_module* zum_save_in_shared_memory(zend_persistent_user_module *pmodule) +{ + uint32_t memory_used; + uint32_t checkpoint; + + if (zend_accel_hash_is_full(&ZCSG(hash))) { + zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Not enough entries in hash table for caching module. Consider increasing the value for the opcache.max_accelerated_files directive in php.ini."); + } + + checkpoint = zend_shared_alloc_checkpoint_xlat_table(); + + /* Calculate the required memory size */ + memory_used = zend_accel_user_module_persist_calc(pmodule, 1); + + /* Allocate shared memory */ + ZCG(mem) = zend_shared_alloc_aligned(memory_used); + if (!ZCG(mem)) { + zend_accel_error_noreturn(ACCEL_LOG_FATAL, "Not enough shared memory for caching module. Consider increasing the value for the opcache.memory_consumption directive in php.ini."); + } + + bzero_aligned(ZCG(mem), memory_used); + + zend_shared_alloc_restore_xlat_table(checkpoint); + + /* Copy into shared memory */ + pmodule = zend_accel_user_module_persist(pmodule, 1); + + // TODO + // new_persistent_script->is_phar = is_phar_file(new_persistent_script->script.filename); + + /* Consistency check */ + if ((char*)pmodule->script->mem + memory_used != (char*)ZCG(mem)) { + zend_accel_error( + ((char*)pmodule->script->mem + pmodule->script->size < (char*)ZCG(mem)) ? ACCEL_LOG_ERROR : ACCEL_LOG_WARNING, + "Internal error: wrong size calculation: module %s start=" ZEND_ADDR_FMT ", end=" ZEND_ADDR_FMT ", real=" ZEND_ADDR_FMT "\n", + ZSTR_VAL(pmodule->module.desc.desc_path), + (size_t)pmodule->script->mem, + (size_t)((char *)pmodule->script->mem + pmodule->script->size), + (size_t)ZCG(mem)); + } + + pmodule->script->dynamic_members.memory_consumption = ZEND_ALIGNED_SIZE(pmodule->script->size); + + return pmodule; +} + +static bool zum_cache_is_valid(zend_file_handle *desc_file_handle, zend_persistent_user_module *pmodule) +{ + if (!ZCG(accel_directives.validate_timestamps)) { + return true; + } + + struct stat st; + if (stat(ZSTR_VAL(desc_file_handle->filename), &st) != 0) { + ZUM_DEBUG_VALIDATION("Could not stat module descriptor: %s\n", ZSTR_VAL(desc_file_handle->filename)); + return false; + } + accel_time_t timestamp = st.st_mtim.tv_sec; + if (timestamp != pmodule->module.desc.timestamp) { + return false; + } + + if (!zum_validate_timestamps(&pmodule->module.desc, pmodule->module.dir_cache)) { + ZUM_DEBUG_VALIDATION("Module timestamps did not validate: %s\n", ZSTR_VAL(desc_file_handle->filename)); + return false; + } + + return true; +} + +static zend_result zum_load(zend_file_handle *desc_file_handle, zend_persistent_user_module *pmodule) +{ + ZUM_DEBUG("Loading cached module: %s\n", ZSTR_VAL(pmodule->module.desc.name)); + + ZEND_ASSERT(zend_accel_in_shm(pmodule)); + + // Register module early to prevent recursive loading in case of cycles + zend_hash_add_ptr(CG(module_table), pmodule->module.desc.lcname, &pmodule->module); + + zend_user_module *dep; + ZEND_HASH_MAP_FOREACH_PTR(&pmodule->module.deps, dep) { + ZEND_ASSERT(zend_accel_in_shm(dep)); + zend_user_module *module = zend_hash_find_ptr(CG(module_table), dep->desc.lcname); + + if (module) { + if (UNEXPECTED(module != dep)) { + /* A different version of the dependency is loaded. This + * invalidates the module. */ + goto fail; + } + continue; + } + + zend_persistent_user_module *pdep = (zend_persistent_user_module*)((char*)dep - XtOffsetOf(zend_persistent_user_module, module)); + + zend_file_handle file_handle; + zend_stream_init_filename_ex(&file_handle, pdep->module.desc.desc_path); + + if (zum_load(&file_handle, pdep) == FAILURE) { + zend_destroy_file_handle(&file_handle); + goto fail; + } + zend_destroy_file_handle(&file_handle); + } ZEND_HASH_FOREACH_END(); + + /* Validate file list and timestamps */ + if (!zum_cache_is_valid(desc_file_handle, pmodule)) { + goto fail; + } + + if (ZCSG(map_ptr_last) > CG(map_ptr_last)) { + zend_map_ptr_extend(ZCSG(map_ptr_last)); + } + + /* Load into process tables */ + +#if 0 + if (zend_hash_num_elements(&pmodule->module.function_table) > 0) { + if (EXPECTED(!zend_observer_function_declared_observed)) { + zend_accel_function_hash_copy(CG(function_table), &pmodule->module.function_table); + } else { + zend_accel_function_hash_copy_notify(CG(function_table), &pmodule->module.function_table); + } + } + + if (zend_hash_num_elements(&pmodule->module.class_table) > 0) { + if (EXPECTED(!zend_observer_class_linked_observed)) { + zend_accel_class_hash_copy(CG(class_table), &pmodule->module.class_table); + } else { + zend_accel_class_hash_copy_notify(CG(class_table), &pmodule->module.class_table); + } + } +#else + if (zend_hash_num_elements(&pmodule->module.function_table)) { + Bucket *p = pmodule->module.function_table.arData; + Bucket *end = p + pmodule->module.function_table.nNumUsed; + HashTable *target = EG(function_table); + + zend_hash_extend(target, + target->nNumUsed + pmodule->module.function_table.nNumUsed, 0); + for (; p != end; p++) { + zval *f = zend_hash_find_known_hash(target, p->key); + if (UNEXPECTED(f != NULL)) { + zend_error_noreturn(E_ERROR, + "Could not load module %s: function %s declared in %s on line %d was previously declared in %s on line %d", + ZSTR_VAL(pmodule->module.desc.name), + ZSTR_VAL(Z_FUNC(p->val)->common.function_name), + ZSTR_VAL(Z_FUNC(p->val)->op_array.filename), + Z_FUNC(p->val)->op_array.line_start, + ZSTR_VAL(Z_FUNC_P(f)->op_array.filename), + Z_FUNC_P(f)->op_array.line_start); + } + _zend_hash_append_ptr_ex(target, p->key, Z_PTR(p->val), 1); + } + } + + if (zend_hash_num_elements(&pmodule->module.class_table)) { + Bucket *p = pmodule->module.class_table.arData; + Bucket *end = p + pmodule->module.class_table.nNumUsed; + HashTable *target = EG(class_table); + + zend_hash_extend(target, + target->nNumUsed + pmodule->module.class_table.nNumUsed, 0); + for (; p != end; p++) { + zval *f = zend_hash_find_known_hash(target, p->key); + if (UNEXPECTED(f != NULL)) { + zend_error_noreturn(E_ERROR, + "Could not load module %s: class %s declared in %s on line %d was previously declared in %s on line %d", + ZSTR_VAL(pmodule->module.desc.name), + ZSTR_VAL(Z_CE(p->val)->name), + ZSTR_VAL(Z_CE(p->val)->info.user.filename), + Z_CE(p->val)->info.user.line_start, + ZSTR_VAL(Z_CE_P(f)->info.user.filename), + Z_CE_P(f)->info.user.line_start); + } + _zend_hash_append_ex(EG(class_table), p->key, &p->val, 1); + } + } +#endif + + zend_hash_add_ptr(CG(module_table), pmodule->module.desc.lcname, &pmodule->module); + + return SUCCESS; + +fail: + zend_hash_del(CG(module_table), pmodule->module.desc.lcname); + + return FAILURE; +} + +static zend_result zum_execute(zend_op_array *op_array) +{ + ZEND_ASSERT(!EG(exception)); + + zend_execute(op_array, NULL); + zend_destroy_static_vars(op_array); + destroy_op_array(op_array); + efree_size(op_array, sizeof(zend_op_array)); + if (UNEXPECTED(EG(exception))) { + return FAILURE; + } + + return SUCCESS; +} + +static zend_class_entry *(*zum_orig_zend_autoload)(zend_string *name, zend_string *lc_name) = NULL; + +static bool zum_autoload_from_modules(zend_string *lc_name) +{ + ZUM_DEBUG("autoload: %s\n", ZSTR_VAL(lc_name)); + + // Find module being loaded whose name is the longer prefix of the class + // name + zend_user_module *candidate; + zend_user_module *module = NULL; + ZEND_HASH_MAP_REVERSE_FOREACH_PTR(CG(module_table), candidate) { + if (candidate->is_loading + && zend_string_starts_with(lc_name, candidate->desc.lcname) + && ZSTR_VAL(lc_name)[ZSTR_LEN(candidate->desc.lcname)] == '\\' + && (module == NULL || ZSTR_LEN(candidate->desc.lcname) > ZSTR_LEN(module->desc.lcname))) { + module = candidate; + } + } ZEND_HASH_FOREACH_END(); + + if (!module) { + return false; + } + + zend_user_module *orig_module = CG(active_module); + CG(active_module) = module; + + zend_persistent_script *persistent_script = zend_hash_find_ptr(&module->classmap, lc_name); + if (!persistent_script) { + goto fail; + } + + if (!zend_hash_add_empty_element(&EG(included_files), persistent_script->script.filename)) { + goto fail; + } + + zend_op_array *op_array = zend_accel_load_script(persistent_script, true); + + ZUM_DEBUG("execute: %s (autoload)\n", ZSTR_VAL(op_array->filename)); + if (zum_execute(op_array) == FAILURE || EG(exception)) { + goto fail; + } + + CG(active_module) = orig_module; + return true; + +fail: + CG(active_module) = orig_module; + return false; +} + +static zend_class_entry *zum_autoload(zend_string *name, zend_string *lc_name) +{ + zend_user_module *module = CG(active_module); + + if (module) { + if (zum_autoload_from_modules(lc_name)) { + if (ZSTR_HAS_CE_CACHE(name) && ZSTR_GET_CE_CACHE(name)) { + return (zend_class_entry*)ZSTR_GET_CE_CACHE(name); + } else { + zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), lc_name); + if (ce) { + return ce; + } + } + } else if (EG(exception)) { + return NULL; + } + } + + return zum_orig_zend_autoload(name, lc_name); +} + +static void zum_handle_loaded_module(zend_user_module *loaded_module, zend_string *module_path) +{ + if (loaded_module->is_loading) { + zend_throw_error(NULL, + "Can not require module %s while it is being loaded", + ZSTR_VAL(loaded_module->desc.name)); + } else { + if (!zend_string_equals(module_path, loaded_module->desc.desc_path)) { + zend_throw_error(NULL, + "Tried to load module %s from %s, but this module was already loaded from %s", + ZSTR_VAL(loaded_module->desc.name), + ZSTR_VAL(module_path), + ZSTR_VAL(loaded_module->desc.desc_path)); + } + } +} + +static void zum_persist_modules(uint32_t module_table_offset); + +ZEND_API void zend_require_user_module(zend_string *module_path) +{ + zend_user_module *orig_module = CG(active_module); + uint32_t module_table_offset = CG(module_table)->nNumUsed; + + /* TODO: move this in MINIT */ + if (zum_orig_zend_autoload == NULL) { + zum_orig_zend_autoload = zend_autoload; + zend_autoload = zum_autoload; + } + + ZUM_DEBUG("require module: %s\n", ZSTR_VAL(module_path)); + + if (!ZCG(accelerator_enabled) && !ZCG(accel_directives).file_cache) { + zend_throw_error(NULL, "require_modules() not supported when opcache is not enabled, yet"); + return; + } + + /* Try loading from cache */ + + zend_file_handle file_handle; + zend_string *full_path = accelerator_orig_zend_resolve_path(module_path); + if (!full_path) { + zend_throw_error(NULL, "Could not resolve module descriptor path: %s\n", + ZSTR_VAL(module_path)); + return; + } + zend_stream_init_filename_ex(&file_handle, full_path); + zend_string_release(full_path); + + if (ZCG(accelerator_enabled)) { + zend_string *module_key = zend_string_concat2( + "module://", strlen("module://"), + ZSTR_VAL(file_handle.filename), ZSTR_LEN(file_handle.filename)); + zend_persistent_user_module *cached_module = zend_accel_hash_find(&ZCSG(hash), module_key); + zend_string_release(module_key); + if (cached_module) { + zend_user_module *loaded_module = zend_hash_find_ptr(EG(module_table), cached_module->module.desc.lcname); + if (loaded_module) { + zum_handle_loaded_module(loaded_module, file_handle.filename); + zend_destroy_file_handle(&file_handle); + return; + } + if (zum_load(&file_handle, cached_module) == SUCCESS) { + zend_destroy_file_handle(&file_handle); + return; + } + } + } + + /* Parse module descriptor */ + + ZUM_DEBUG("no valid cache for module %s\n", ZSTR_VAL(module_path)); + + struct stat st; + if (stat(ZSTR_VAL(file_handle.filename), &st) != 0) { + zend_throw_error(NULL, "Could not stat module descriptor: %s\n", ZSTR_VAL(file_handle.filename)); + return; + } + zend_user_module_desc module_desc = { + .desc_path = zend_string_copy(file_handle.filename), + .timestamp = st.st_mtim.tv_sec, + }; + + if (zend_parse_ini_file(&file_handle, 0, ZEND_INI_SCANNER_NORMAL, zum_desc_parser_cb, &module_desc) == FAILURE) { + zend_destroy_file_handle(&file_handle); + zum_desc_destroy(&module_desc); + return; + } + + module_desc.lcname = zend_string_tolower(module_desc.name); + + /* Check if module with same namespace is already loaded */ + + zend_user_module *loaded_module = zend_hash_find_ptr(EG(module_table), module_desc.lcname); + if (loaded_module) { + zum_handle_loaded_module(loaded_module, file_handle.filename); + zend_destroy_file_handle(&file_handle); + zum_desc_destroy(&module_desc); + return; + } + + char *dirname = estrndup(ZSTR_VAL(file_handle.opened_path), ZSTR_LEN(file_handle.opened_path)); + size_t dirname_len = zend_dirname(dirname, ZSTR_LEN(file_handle.opened_path)); + module_desc.root = zend_string_concat2( + dirname, dirname_len, + DEFAULT_SLASH_STR, strlen(DEFAULT_SLASH_STR)); + efree(dirname); + + zend_destroy_file_handle(&file_handle); + + zend_user_module *module = emalloc(sizeof(zend_user_module)); + module->desc = module_desc; + module->is_loading = true; + module->is_persistent = false; + zend_hash_init(&module->scripts, 0, NULL, NULL, 0); + zend_hash_init(&module->classmap, 0, NULL, NULL, 0); + zend_hash_init(&module->class_table, 0, NULL, NULL, 0); + zend_hash_init(&module->function_table, 0, NULL, NULL, 0); + zend_hash_init(&module->deps, 0, NULL, NULL, 0); + + CG(active_module) = module; + + zend_hash_update_ptr(EG(module_table), module->desc.lcname, module); + + /* List files */ + + zend_user_module_dir_cache *dir_cache = NULL; + HashTable filenames; + zend_hash_init(&filenames, 0, NULL, NULL, 0); + zum_find_files(&module_desc, &dir_cache, &filenames); + if (filenames.nNumOfElements == 0) { + zend_error_noreturn(E_CORE_ERROR, "No files matched"); + } + + /* Compile files to build the classmap */ + + uint32_t class_table_offset = CG(class_table)->nNumUsed; + uint32_t function_table_offset = CG(function_table)->nNumUsed; + + zend_string *file; + zval *val; + ZEND_HASH_FOREACH_STR_KEY_VAL(&filenames, file, val) { + uint32_t file_class_table_offset = CG(class_table)->nNumUsed; + + zend_file_handle file_handle; + zend_stream_init_filename(&file_handle, ZSTR_VAL(file)); + + zend_persistent_script *persistent_script = zum_compile_file(module, &file_handle); + if (!persistent_script) { + zend_destroy_file_handle(&file_handle); + return; + } + + ZVAL_PTR(val, persistent_script); + + zend_destroy_file_handle(&file_handle); + + zend_class_entry *ce; + ZEND_HASH_FOREACH_PTR_FROM(CG(class_table), ce, file_class_table_offset) { + ZEND_ASSERT(zend_string_equals(ce->info.user.user_module, module->desc.lcname)); + ZEND_ASSERT(ce->type == ZEND_USER_CLASS); + zend_string *lcname = zend_string_tolower(ce->name); + zend_hash_add_ptr(&module->classmap, lcname, persistent_script); + zend_string_release(lcname); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); + + // Revert any compile-time declarations + zend_class_entry *ce; + ZEND_HASH_MAP_FOREACH_PTR_FROM(CG(class_table), ce, class_table_offset) { + zend_string *name = ce->name; + if (ZSTR_HAS_CE_CACHE(name)) { + ZSTR_SET_CE_CACHE(name, NULL); + } + } ZEND_HASH_FOREACH_END(); + zend_hash_discard(EG(class_table), class_table_offset); + zend_hash_discard(EG(function_table), function_table_offset); + + /* Execute files */ + + zend_hash_init(&module->class_table, module->classmap.nNumOfElements, NULL, ZEND_CLASS_DTOR, 0); + zend_hash_init(&module->function_table, 0, NULL, ZEND_FUNCTION_DTOR, 0); + + /* If we were called from autoloading, forget it because we may need to + * trigger autoloading for that class again */ + if (EG(in_autoload)) { + HashPosition pos; + zval key; + zend_hash_internal_pointer_end_ex(EG(in_autoload), &pos); + zend_hash_get_current_key_zval_ex(EG(in_autoload), &key, &pos); + if (Z_TYPE(key) == IS_STRING) { + zend_string *lc_name = Z_STR(key); + if (zend_string_starts_with(lc_name, module->desc.lcname) + && ZSTR_VAL(lc_name)[ZSTR_LEN(module->desc.lcname)] == '\\') { + zend_hash_del(EG(in_autoload), lc_name); + // zum_autoload_internal(lc_name); + } + zend_string_release(Z_STR(key)); + } + } + + zend_persistent_script *persistent_script; + + ZEND_HASH_FOREACH_PTR(&filenames, persistent_script) { + if (!persistent_script) { // TODO: is this possible? + continue; + } + if (!zum_op_array_has_stmts(&persistent_script->script.main_op_array)) { + continue; + } + if (!zend_hash_add_empty_element(&EG(included_files), persistent_script->script.filename)) { + continue; + } + ZUM_DEBUG("execute: %s\n", ZSTR_VAL(persistent_script->script.filename)); + zend_op_array *op_array = zend_accel_load_script(persistent_script, true); + if (zum_execute(op_array) == FAILURE) { + goto cleanup; + } + } ZEND_HASH_FOREACH_END(); + + ZEND_HASH_FOREACH_PTR(&filenames, persistent_script) { + if (!persistent_script) { // TODO: is this possible? + continue; + } + if (!zend_hash_add_empty_element(&EG(included_files), persistent_script->script.filename)) { + continue; + } + ZUM_DEBUG("execute: %s\n", ZSTR_VAL(persistent_script->script.filename)); + zend_op_array *op_array = zend_accel_load_script(persistent_script, true); + if (zum_execute(op_array) == FAILURE) { + goto cleanup; + } + } ZEND_HASH_FOREACH_END(); + + /* Enforce that referenced symbols exist and build class/function tables */ + + if (zum_check_deps(module, class_table_offset, function_table_offset) == FAILURE) { + ZEND_ASSERT(EG(exception)); + goto cleanup; + } + +cleanup: + { + zend_hash_destroy(&module->scripts); + zend_hash_destroy(&module->classmap); + + zend_hash_destroy(&filenames); + + CG(active_module) = orig_module; + module->is_loading = false; + module->dir_cache = dir_cache; + + if (EG(exception)) { + zum_desc_destroy(&module_desc); + zend_hash_del(EG(module_table), module->desc.lcname); + efree(module); + } else if (!orig_module) { + zum_persist_modules(module_table_offset); + } + } +} + +static zend_persistent_user_module *zum_build_persistent_module(zend_user_module *module) +{ + ZUM_DEBUG("Build persistent module %s\n", ZSTR_VAL(module->desc.name)); + + ZEND_ASSERT(!module->is_loading); + ZEND_ASSERT(!zend_accel_in_shm(module)); + + zend_string *orig_compiled_filename = CG(compiled_filename); + CG(compiled_filename) = zend_strpprintf(0, "module://%s", ZSTR_VAL(module->desc.desc_path)); + + zend_persistent_script *pscript = emalloc(sizeof(zend_persistent_script)); + memset(pscript, 0, sizeof(zend_persistent_script)); + + zend_hash_init(&pscript->script.class_table, 0, NULL, NULL, 0); + zend_hash_init(&pscript->script.function_table, 0, NULL, NULL, 0); + pscript->script.filename = CG(compiled_filename); + + zend_script *script = &pscript->script; + +#if ZEND_USE_ABS_CONST_ADDR + init_op_array(&script->main_op_array, ZEND_USER_FUNCTION, 1); +#else + init_op_array(&script->main_op_array, ZEND_USER_FUNCTION, 2); +#endif + script->main_op_array.fn_flags |= ZEND_ACC_DONE_PASS_TWO; + script->main_op_array.last = 1; + script->main_op_array.last_literal = 1; + script->main_op_array.T = ZEND_OBSERVER_ENABLED; +#if ZEND_USE_ABS_CONST_ADDR + script->main_op_array.literals = (zval*)emalloc(sizeof(zval)); +#else + script->main_op_array.literals = (zval*)(script->main_op_array.opcodes + 1); +#endif + ZVAL_NULL(script->main_op_array.literals); + memset(script->main_op_array.opcodes, 0, sizeof(zend_op)); + script->main_op_array.opcodes[0].opcode = ZEND_RETURN; + script->main_op_array.opcodes[0].op1_type = IS_CONST; + script->main_op_array.opcodes[0].op1.constant = 0; + ZEND_PASS_TWO_UPDATE_CONSTANT(&script->main_op_array, script->main_op_array.opcodes, script->main_op_array.opcodes[0].op1); + zend_vm_set_opcode_handler(script->main_op_array.opcodes); + + zend_persistent_user_module *pmodule = emalloc(sizeof(zend_persistent_user_module)); + memset(pmodule, 0, sizeof(zend_persistent_user_module)); + + pmodule->script = pscript; + pmodule->module = *module; + pmodule->module.is_persistent = true; + + CG(compiled_filename) = orig_compiled_filename; + + zend_shared_alloc_register_xlat_entry(module, pmodule); + + return pmodule; +} + +static void zum_persist_modules(uint32_t module_table_offset) +{ + ZEND_ASSERT(!CG(active_module)); + + if (ZCG(accel_directives).file_cache_only) { + // TODO: leaks + return; + } + + zend_shared_alloc_init_xlat_table(); + + /* Replace zend_user_modules by zend_persistent_user_modules + * in module_table and module dependencies. */ + + zval *zv; + ZEND_HASH_MAP_FOREACH_VAL_FROM(CG(module_table), zv, module_table_offset) { + zend_user_module *module = Z_PTR_P(zv); + if (zend_accel_in_shm(module)) { + continue; + } + zend_persistent_user_module *pmodule = zum_build_persistent_module(module); + Z_PTR_P(zv) = &pmodule->module; + efree(module); + } ZEND_HASH_FOREACH_END(); + + zend_user_module *module; + ZEND_HASH_MAP_FOREACH_PTR_FROM(CG(module_table), module, module_table_offset) { + if (zend_accel_in_shm(module)) { + continue; + } + ZEND_HASH_MAP_FOREACH_VAL(&module->deps, zv) { + zend_user_module *dep = Z_PTR_P(zv); + zend_persistent_user_module *pdep = zend_shared_alloc_get_xlat_entry(dep); + if (!pdep) { + ZEND_ASSERT(zend_accel_in_shm(dep)); + continue; + } + Z_PTR_P(zv) = &pdep->module; + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); + + zend_shared_alloc_clear_xlat_table(); + + HANDLE_BLOCK_INTERRUPTIONS(); + SHM_UNPROTECT(); + zend_shared_alloc_lock(); + + ZEND_HASH_MAP_FOREACH_VAL_FROM(CG(module_table), zv, module_table_offset) { + zend_user_module *module = Z_PTR_P(zv); + zend_persistent_user_module *pmodule = (zend_persistent_user_module*)((char*)module - XtOffsetOf(zend_persistent_user_module, module)); + zend_persistent_user_module *persisted_pmodule = zend_shared_alloc_get_xlat_entry(pmodule); + if (!persisted_pmodule) { + if (zend_accel_in_shm(pmodule)) { + continue; + } + persisted_pmodule = zum_save_in_shared_memory(pmodule); + } + Z_PTR_P(zv) = &persisted_pmodule->module; + zend_shared_alloc_register_xlat_entry(persisted_pmodule, persisted_pmodule); + } ZEND_HASH_FOREACH_END(); + + ZEND_HASH_MAP_FOREACH_PTR_FROM(CG(module_table), module, module_table_offset) { + zend_persistent_user_module *pmodule = (zend_persistent_user_module*)((char*)module - XtOffsetOf(zend_persistent_user_module, module)); + if (!zend_shared_alloc_get_xlat_entry(pmodule)) { + continue; + } + + zend_string *key = accel_new_interned_string(zend_string_concat2( + "module://", strlen("module://"), + ZSTR_VAL(pmodule->module.desc.desc_path), ZSTR_LEN(pmodule->module.desc.desc_path))); + if (!ZSTR_IS_INTERNED(key)) { + // TODO + zend_error_noreturn(E_CORE_ERROR, "Could not intern module key (increase opcache.interned_strings_buffer size)"); + } + + zend_accel_hash_entry *bucket = zend_accel_hash_update(&ZCSG(hash), key, 0, pmodule); + zend_string_release(key); + if (bucket) { + zend_accel_error(ACCEL_LOG_INFO, "Cached module '%s'", ZSTR_VAL(pmodule->module.desc.desc_path)); + } + } ZEND_HASH_FOREACH_END(); + + zend_shared_alloc_save_state(); + ZCSG(interned_strings).saved_top = ZCSG(interned_strings).top; + zend_shared_alloc_unlock(); + SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); + + zend_shared_alloc_destroy_xlat_table(); +} diff --git a/ext/opcache/modules.h b/ext/opcache/modules.h new file mode 100644 index 0000000000000..2284ba43b3c30 --- /dev/null +++ b/ext/opcache/modules.h @@ -0,0 +1,31 @@ + +#ifndef ZEND_MODULES_H +#define ZEND_MODULES_H + +#include "Zend/zend_types.h" +#include "Zend/zend_compile.h" +#include "ZendAccelerator.h" + +// Not using zend_value.ptr as it may be too small on x32 +#define ZUM_IS_FILE_ENTRY(zv) ((bool)((*(uintptr_t*)&(zv)->value) & 1)) +#define ZUM_FILE_ENTRY(zv, mtime) \ + do { \ + ((*(uintptr_t*)&(zv)->value) = (((mtime) << 1) | 1)); \ + Z_TYPE_INFO_P(zv) = IS_PTR; \ + } while (0) +#define ZUM_FILE_ENTRY_MTIME(zv) ((accel_time_t)((*(uintptr_t*)&(zv)->value) >> 1)) +#define ZUM_DIR_ENTRY(zv) ((zend_user_module_dir_cache*)Z_PTR_P(zv)) + +typedef struct _zend_user_module_dir_cache { + accel_time_t mtime; + HashTable entries; +} zend_user_module_dir_cache; + +typedef struct _zend_persistent_user_module { + zend_user_module module; + zend_persistent_script *script; +} zend_persistent_user_module; + +ZEND_API void zend_require_user_module(zend_string *module_path); + +#endif /* ZEND_MODULES_H */ diff --git a/ext/opcache/opcache.stub.php b/ext/opcache/opcache.stub.php index 526da238219a4..35780317f7814 100644 --- a/ext/opcache/opcache.stub.php +++ b/ext/opcache/opcache.stub.php @@ -23,3 +23,5 @@ function opcache_jit_blacklist(Closure $closure): void {} function opcache_get_configuration(): array|false {} function opcache_is_script_cached(string $filename): bool {} + +function require_modules(array $modules): void {} diff --git a/ext/opcache/opcache_arginfo.h b/ext/opcache/opcache_arginfo.h index b4dc1f33a5fd8..1d4225b7e9200 100644 --- a/ext/opcache/opcache_arginfo.h +++ b/ext/opcache/opcache_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: c416c231c5d1270b7e5961f84cc3ca3e29db4959 */ + * Stub hash: 38ccfbe9fdf851de4c377bd55bf957bb0160511b */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_opcache_reset, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -26,6 +26,10 @@ ZEND_END_ARG_INFO() #define arginfo_opcache_is_script_cached arginfo_opcache_compile_file +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_require_modules, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, modules, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + ZEND_FUNCTION(opcache_reset); ZEND_FUNCTION(opcache_get_status); ZEND_FUNCTION(opcache_compile_file); @@ -33,6 +37,7 @@ ZEND_FUNCTION(opcache_invalidate); ZEND_FUNCTION(opcache_jit_blacklist); ZEND_FUNCTION(opcache_get_configuration); ZEND_FUNCTION(opcache_is_script_cached); +ZEND_FUNCTION(require_modules); static const zend_function_entry ext_functions[] = { ZEND_FE(opcache_reset, arginfo_opcache_reset) @@ -42,5 +47,6 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(opcache_jit_blacklist, arginfo_opcache_jit_blacklist) ZEND_FE(opcache_get_configuration, arginfo_opcache_get_configuration) ZEND_FE(opcache_is_script_cached, arginfo_opcache_is_script_cached) + ZEND_FE(require_modules, arginfo_require_modules) ZEND_FE_END }; diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index 1b62b757b87d2..3a639bad4a4de 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -25,15 +25,19 @@ #include "ZendAccelerator.h" #include "zend_API.h" #include "zend_closures.h" +#include "zend_hash.h" +#include "zend_operators.h" #include "zend_shared_alloc.h" #include "zend_accelerator_blacklist.h" #include "php_ini.h" #include "SAPI.h" +#include "zend_types.h" #include "zend_virtual_cwd.h" #include "ext/standard/info.h" #include "ext/standard/php_filestat.h" #include "ext/date/php_date.h" #include "opcache_arginfo.h" +#include "modules.h" #ifdef HAVE_JIT #include "jit/zend_jit.h" @@ -598,6 +602,11 @@ static int accelerator_get_scripts(zval *return_value) script = (zend_persistent_script *)cache_entry->data; + // TODO + if (strncmp(cache_entry->key->val, "module://", strlen("module://")) == 0) { + continue; + } + array_init(&persistent_script_report); add_assoc_str(&persistent_script_report, "full_path", zend_string_dup(script->script.filename, 0)); add_assoc_long(&persistent_script_report, "hits", script->dynamic_members.hits); @@ -998,3 +1007,24 @@ ZEND_FUNCTION(opcache_is_script_cached) RETURN_BOOL(filename_is_in_cache(script_name)); } + +ZEND_FUNCTION(require_modules) +{ + HashTable *modules; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY_HT(modules) + ZEND_PARSE_PARAMETERS_END(); + + zval *value; + ZEND_HASH_FOREACH_VAL(modules, value) { + convert_to_string(value); + if (UNEXPECTED(EG(exception))) { + return; + } + zend_require_user_module(Z_STR_P(value)); + if (UNEXPECTED(EG(exception))) { + return; + } + } ZEND_HASH_FOREACH_END(); +} diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index 716a6e4df0094..94973d9db729f 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -187,12 +187,12 @@ static zend_always_inline void _zend_accel_function_hash_copy(HashTable *target, } } -static zend_always_inline void zend_accel_function_hash_copy(HashTable *target, HashTable *source) +zend_always_inline void zend_accel_function_hash_copy(HashTable *target, HashTable *source) { _zend_accel_function_hash_copy(target, source, 0); } -static zend_never_inline void zend_accel_function_hash_copy_notify(HashTable *target, HashTable *source) +zend_never_inline void zend_accel_function_hash_copy_notify(HashTable *target, HashTable *source) { _zend_accel_function_hash_copy(target, source, 1); } @@ -248,12 +248,12 @@ static zend_always_inline void _zend_accel_class_hash_copy(HashTable *target, Ha target->nInternalPointer = 0; } -static zend_always_inline void zend_accel_class_hash_copy(HashTable *target, HashTable *source) +zend_always_inline void zend_accel_class_hash_copy(HashTable *target, HashTable *source) { _zend_accel_class_hash_copy(target, source, 0); } -static zend_never_inline void zend_accel_class_hash_copy_notify(HashTable *target, HashTable *source) +zend_never_inline void zend_accel_class_hash_copy_notify(HashTable *target, HashTable *source) { _zend_accel_class_hash_copy(target, source, 1); } @@ -421,6 +421,13 @@ zend_op_array* zend_accel_load_script(zend_persistent_script *persistent_script, free_persistent_script(persistent_script, 0); /* free only hashes */ } + if (CG(active_module) && persistent_script->script.main_op_array.user_module) { + zend_op_array *op_array = &persistent_script->script.main_op_array; + ZEND_ASSERT(zend_string_equals(op_array->user_module, CG(active_module)->desc.lcname)); + zend_hash_update_ptr(&CG(active_module)->scripts, + op_array->filename, persistent_script); + } + return op_array; } diff --git a/ext/opcache/zend_accelerator_util_funcs.h b/ext/opcache/zend_accelerator_util_funcs.h index 53cc1de9effaa..2d86a2707c385 100644 --- a/ext/opcache/zend_accelerator_util_funcs.h +++ b/ext/opcache/zend_accelerator_util_funcs.h @@ -44,6 +44,12 @@ unsigned int zend_adler32(unsigned int checksum, unsigned char *buf, uint32_t le unsigned int zend_accel_script_checksum(zend_persistent_script *persistent_script); +void zend_accel_function_hash_copy(HashTable *target, HashTable *source); +void zend_accel_function_hash_copy_notify(HashTable *target, HashTable *source); + +void zend_accel_class_hash_copy(HashTable *target, HashTable *source); +void zend_accel_class_hash_copy_notify(HashTable *target, HashTable *source); + END_EXTERN_C() #endif /* ZEND_ACCELERATOR_UTIL_FUNCS_H */ diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index f12170e8c9936..f29f0e3784944 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -484,6 +484,7 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra SERIALIZE_PTR(op_array->arg_info); SERIALIZE_PTR(op_array->vars); SERIALIZE_STR(op_array->function_name); + SERIALIZE_STR(op_array->user_module); SERIALIZE_STR(op_array->filename); SERIALIZE_PTR(op_array->live_range); SERIALIZE_PTR(op_array->scope); @@ -636,6 +637,7 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra } SERIALIZE_STR(op_array->function_name); + SERIALIZE_STR(op_array->user_module); SERIALIZE_STR(op_array->filename); SERIALIZE_PTR(op_array->live_range); SERIALIZE_PTR(op_array->scope); @@ -772,6 +774,7 @@ static void zend_file_cache_serialize_class(zval *zv, } } zend_file_cache_serialize_hash(&ce->constants_table, script, info, buf, zend_file_cache_serialize_class_constant); + SERIALIZE_STR(ce->info.user.user_module); SERIALIZE_STR(ce->info.user.filename); SERIALIZE_STR(ce->doc_comment); SERIALIZE_ATTRIBUTES(ce->attributes); @@ -1373,6 +1376,7 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr UNSERIALIZE_PTR(op_array->arg_info); UNSERIALIZE_PTR(op_array->vars); UNSERIALIZE_STR(op_array->function_name); + UNSERIALIZE_STR(op_array->user_module); UNSERIALIZE_STR(op_array->filename); UNSERIALIZE_PTR(op_array->live_range); UNSERIALIZE_PTR(op_array->scope); @@ -1509,6 +1513,7 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr } UNSERIALIZE_STR(op_array->function_name); + UNSERIALIZE_STR(op_array->user_module); UNSERIALIZE_STR(op_array->filename); UNSERIALIZE_PTR(op_array->live_range); UNSERIALIZE_STR(op_array->doc_comment); @@ -1637,6 +1642,7 @@ static void zend_file_cache_unserialize_class(zval *zv, } zend_file_cache_unserialize_hash(&ce->constants_table, script, buf, zend_file_cache_unserialize_class_constant, NULL); + UNSERIALIZE_STR(ce->info.user.user_module); UNSERIALIZE_STR(ce->info.user.filename); UNSERIALIZE_STR(ce->doc_comment); UNSERIALIZE_ATTRIBUTES(ce->attributes); @@ -2022,7 +2028,7 @@ void zend_file_cache_invalidate(zend_string *full_path) if (ZCG(accel_directives).file_cache_read_only) { return; } - + char *filename; filename = zend_file_cache_get_bin_file_path(full_path); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 11babd8aa4067..002e38a31319d 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -23,12 +23,15 @@ #include "ZendAccelerator.h" #include "zend_persist.h" #include "zend_extensions.h" +#include "zend_portability.h" #include "zend_shared_alloc.h" +#include "zend_types.h" #include "zend_vm.h" #include "zend_constants.h" #include "zend_operators.h" #include "zend_interfaces.h" #include "zend_attributes.h" +#include "modules.h" #ifdef HAVE_JIT # include "Optimizer/zend_func_info.h" @@ -398,6 +401,10 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc EG(current_execute_data) = orig_execute_data; } + if (op_array->user_module) { + zend_accel_store_string(op_array->user_module); + } + if (op_array->function_name) { zend_string *old_name = op_array->function_name; zend_accel_store_interned_string(op_array->function_name); @@ -1022,6 +1029,9 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) ce->ce_flags |= ZEND_ACC_CACHED; + if (ce->info.user.user_module) { + zend_accel_store_string(ce->info.user.user_module); + } if (ce->info.user.filename) { zend_accel_store_string(ce->info.user.filename); } @@ -1433,3 +1443,106 @@ zend_persistent_script *zend_accel_script_persist(zend_persistent_script *script return script; } + +zend_user_module_dir_cache *zend_accel_user_module_dir_cache_persist(zend_user_module_dir_cache *cache) +{ + cache = zend_shared_memdup_free(cache, sizeof(zend_user_module_dir_cache)); + + Bucket *p; + ZEND_HASH_MAP_FOREACH_BUCKET(&cache->entries, p) { + ZEND_ASSERT(p->key != NULL); + zend_accel_store_interned_string(p->key); + if (!ZUM_IS_FILE_ENTRY(&p->val)) { + Z_PTR(p->val) = zend_accel_user_module_dir_cache_persist(Z_PTR(p->val)); + } + } ZEND_HASH_FOREACH_END(); + + zend_hash_persist(&cache->entries); + + return cache; +} + +zend_persistent_user_module *zend_accel_user_module_persist(zend_persistent_user_module *module, int for_shm) +{ + module->script = zend_accel_script_persist(module->script, for_shm); + + ZEND_ASSERT(((uintptr_t)ZCG(mem) & 0x7) == 0); /* should be 8 byte aligned */ + + ZCG(current_persistent_script) = module->script; + + module = zend_shared_memdup_put_free(module, sizeof(*module)); + + zend_accel_store_interned_string(module->module.desc.desc_path); + zend_accel_store_interned_string(module->module.desc.name); + zend_accel_store_interned_string(module->module.desc.lcname); + zend_accel_store_interned_string(module->module.desc.root); + if (module->module.desc.include_patterns) { + module->module.desc.include_patterns = zend_shared_memdup_free( + module->module.desc.include_patterns, + sizeof(*module->module.desc.include_patterns) * module->module.desc.num_include_patterns); + zend_string **str = module->module.desc.include_patterns; + zend_string **end = module->module.desc.include_patterns + module->module.desc.num_include_patterns; + while (str < end) { + zend_accel_store_interned_string(*str); + str++; + } + } + if (module->module.desc.exclude_patterns) { + module->module.desc.exclude_patterns = zend_shared_memdup_free( + module->module.desc.exclude_patterns, + sizeof(*module->module.desc.exclude_patterns) * module->module.desc.num_exclude_patterns); + zend_string **str = module->module.desc.exclude_patterns; + zend_string **end = module->module.desc.exclude_patterns + module->module.desc.num_exclude_patterns; + while (str < end) { + zend_accel_store_interned_string(*str); + str++; + } + } + + zend_hash_persist(&module->module.deps); + + Bucket *p; + + zend_hash_persist(&module->module.class_table); + ZEND_HASH_MAP_FOREACH_BUCKET(&module->module.class_table, p) { + ZEND_ASSERT(p->key != NULL); + zend_accel_store_interned_string(p->key); + } ZEND_HASH_FOREACH_END(); + + zend_hash_persist(&module->module.function_table); + ZEND_HASH_MAP_FOREACH_BUCKET(&module->module.function_table, p) { + ZEND_ASSERT(p->key != NULL); + zend_accel_store_interned_string(p->key); + } ZEND_HASH_FOREACH_END(); + + module->module.dir_cache = zend_accel_user_module_dir_cache_persist(module->module.dir_cache); + + // TODO: We need these during compilation, but not after. Maybe store these + // elsewhere. + memset(&module->module.classmap, 0, sizeof(HashTable)); + +#if defined(__AVX__) || defined(__SSE2__) + /* Align to 64-byte boundary */ + ZCG(mem) = (void*)(((uintptr_t)ZCG(mem) + 63L) & ~63L); +#else + ZEND_ASSERT(((uintptr_t)ZCG(mem) & 0x7) == 0); /* should be 8 byte aligned */ +#endif + + ZCG(current_persistent_script) = NULL; + + zval *zv; + ZEND_HASH_MAP_FOREACH_VAL(&module->module.deps, zv) { + zend_user_module *dep = Z_PTR_P(zv); + if (zend_accel_in_shm(dep)) { + continue; + } + zend_persistent_user_module *pdep = (zend_persistent_user_module*)((char*)dep - XtOffsetOf(zend_persistent_user_module, module)); + zend_persistent_user_module *persisted_pdep = zend_shared_alloc_get_xlat_entry(pdep); + if (!persisted_pdep) { + persisted_pdep = zend_accel_user_module_persist(pdep, for_shm); + } + Z_PTR_P(zv) = &persisted_pdep->module; + } ZEND_HASH_FOREACH_END(); + + return module; +} diff --git a/ext/opcache/zend_persist.h b/ext/opcache/zend_persist.h index 930430c9589b7..f5a7aac21bae3 100644 --- a/ext/opcache/zend_persist.h +++ b/ext/opcache/zend_persist.h @@ -22,10 +22,14 @@ #ifndef ZEND_PERSIST_H #define ZEND_PERSIST_H +#include "modules.h" + BEGIN_EXTERN_C() uint32_t zend_accel_script_persist_calc(zend_persistent_script *script, int for_shm); zend_persistent_script *zend_accel_script_persist(zend_persistent_script *script, int for_shm); +uint32_t zend_accel_user_module_persist_calc(zend_persistent_user_module *module, int for_shm); +zend_persistent_user_module *zend_accel_user_module_persist(zend_persistent_user_module *module, int for_shm); void zend_persist_class_entry_calc(zend_class_entry *ce); zend_class_entry *zend_persist_class_entry(zend_class_entry *ce); diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index fe58d3f2de277..b719533994d33 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -19,8 +19,10 @@ +----------------------------------------------------------------------+ */ +#include "modules.h" #include "zend.h" #include "ZendAccelerator.h" +#include "zend_hash.h" #include "zend_persist.h" #include "zend_extensions.h" #include "zend_shared_alloc.h" @@ -205,6 +207,10 @@ static void zend_persist_type_calc(zend_type *type) static void zend_persist_op_array_calc_ex(zend_op_array *op_array) { + if (op_array->user_module) { + ADD_STRING(op_array->user_module); + } + if (op_array->function_name) { zend_string *old_name = op_array->function_name; ADD_INTERNED_STRING(op_array->function_name); @@ -323,6 +329,7 @@ static void zend_persist_op_array_calc(zval *zv) { zend_op_array *op_array = Z_PTR_P(zv); ZEND_ASSERT(op_array->type == ZEND_USER_FUNCTION); + if (!zend_shared_alloc_get_xlat_entry(op_array)) { zend_shared_alloc_register_xlat_entry(op_array, op_array); ADD_SIZE(sizeof(zend_op_array)); @@ -499,6 +506,9 @@ void zend_persist_class_entry_calc(zend_class_entry *ce) return; } + if (ce->info.user.user_module) { + ADD_STRING(ce->info.user.user_module); + } if (ce->info.user.filename) { ADD_STRING(ce->info.user.filename); } @@ -651,3 +661,92 @@ uint32_t zend_accel_script_persist_calc(zend_persistent_script *new_persistent_s return new_persistent_script->size; } + +void zend_accel_user_module_dir_cache_persist_calc(zend_user_module_dir_cache *cache) +{ + ADD_SIZE(sizeof(*cache)); + + Bucket *p; + ZEND_HASH_MAP_FOREACH_BUCKET(&cache->entries, p) { + ZEND_ASSERT(p->key != NULL); + ADD_INTERNED_STRING(p->key); + if (!ZUM_IS_FILE_ENTRY(&p->val)) { + zend_accel_user_module_dir_cache_persist_calc(Z_PTR(p->val)); + } + } ZEND_HASH_FOREACH_END(); + + zend_hash_persist_calc(&cache->entries); +} + +uint32_t zend_accel_user_module_persist_calc(zend_persistent_user_module *module, int for_shm) +{ + zend_accel_script_persist_calc(module->script, for_shm); + + ZCG(current_persistent_script) = module->script; + + ADD_SIZE(sizeof(zend_persistent_user_module)); + zend_shared_alloc_register_xlat_entry(module, module); + + ADD_INTERNED_STRING(module->module.desc.desc_path); + ADD_INTERNED_STRING(module->module.desc.name); + ADD_INTERNED_STRING(module->module.desc.lcname); + ADD_INTERNED_STRING(module->module.desc.root); + if (module->module.desc.include_patterns) { + ADD_SIZE(sizeof(*module->module.desc.include_patterns) * module->module.desc.num_include_patterns); + zend_string **str = module->module.desc.include_patterns; + zend_string **end = module->module.desc.include_patterns + module->module.desc.num_include_patterns; + while (str < end) { + ADD_INTERNED_STRING(*str); + str++; + } + } + if (module->module.desc.exclude_patterns) { + ADD_SIZE(sizeof(*module->module.desc.exclude_patterns) * module->module.desc.num_exclude_patterns); + zend_string **str = module->module.desc.exclude_patterns; + zend_string **end = module->module.desc.exclude_patterns + module->module.desc.num_exclude_patterns; + while (str < end) { + ADD_INTERNED_STRING(*str); + str++; + } + } + + zend_hash_persist_calc(&module->module.deps); + + Bucket *p; + + zend_hash_persist_calc(&module->module.class_table); + ZEND_HASH_MAP_FOREACH_BUCKET(&module->module.class_table, p) { + ZEND_ASSERT(p->key != NULL); + ADD_INTERNED_STRING(p->key); + } ZEND_HASH_FOREACH_END(); + + zend_hash_persist_calc(&module->module.function_table); + ZEND_HASH_MAP_FOREACH_BUCKET(&module->module.function_table, p) { + ZEND_ASSERT(p->key != NULL); + ADD_INTERNED_STRING(p->key); + } ZEND_HASH_FOREACH_END(); + + zend_accel_user_module_dir_cache_persist_calc(module->module.dir_cache); + +#if defined(__AVX__) || defined(__SSE2__) + /* Align size to 64-byte boundary */ + module->script->size = (module->script->size + 63) & ~63; +#endif + + ZCG(current_persistent_script) = NULL; + + size_t size = module->script->size; + + zend_user_module *dep; + ZEND_HASH_MAP_FOREACH_PTR(&module->module.deps, dep) { + if (zend_accel_in_shm(dep)) { + continue; + } + zend_persistent_user_module *pdep = (zend_persistent_user_module*)((char*)dep - XtOffsetOf(zend_persistent_user_module, module)); + if (!zend_shared_alloc_get_xlat_entry(pdep)) { + size += zend_accel_user_module_persist_calc(pdep, for_shm); + } + } ZEND_HASH_FOREACH_END(); + + return size; +} diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index f5e463699b1b5..d8c8eb564aa20 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -24,6 +24,7 @@ #include "zend_object_handlers.h" #include "zend_type_info.h" #include "zend_types.h" +#include "zend_compile.h" #ifdef HAVE_CONFIG_H #include #endif @@ -5514,6 +5515,42 @@ ZEND_METHOD(ReflectionClass, getExtensionName) } /* }}} */ +/* {{{ Returns whether this class is defined in a module */ +ZEND_METHOD(ReflectionClass, inModule) +{ + reflection_object *intern; + zend_class_entry *ce; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ce); + + RETURN_BOOL(ce->type == ZEND_USER_CLASS && ce->info.user.user_module); +} +/* }}} */ + +/* {{{ Returns the name of the module where this class is defined */ +ZEND_METHOD(ReflectionClass, getModuleName) +{ + reflection_object *intern; + zend_class_entry *ce; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + + GET_REFLECTION_OBJECT_PTR(ce); + + if (ce->type == ZEND_USER_CLASS && ce->info.user.user_module) { + zend_user_module *module = zend_hash_find_ptr(CG(module_table), ce->info.user.user_module); + RETURN_STR(module->desc.name); + } + RETURN_EMPTY_STRING(); +} +/* }}} */ + /* {{{ Returns whether this class is defined in namespace */ ZEND_METHOD(ReflectionClass, inNamespace) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index be511d7ee14cd..8d445b5b2e650 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -422,6 +422,12 @@ public function getExtension(): ?ReflectionExtension {} /** @tentative-return-type */ public function getExtensionName(): string|false {} + /** @tentative-return-type */ + public function inModule(): bool {} + + /** @tentative-return-type */ + public function getModuleName(): string {} + /** @tentative-return-type */ public function inNamespace(): bool {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index d78a685dde9c9..a89bd51f35c81 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 3c6be99bb36965139464925a618cb0bf03affa62 */ + * Stub hash: ef7fb72873c99492cad4a17178bbf6abd13c6972 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -358,6 +358,10 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClass_getExtensionName arginfo_class_ReflectionFunctionAbstract_getDocComment +#define arginfo_class_ReflectionClass_inModule arginfo_class_ReflectionFunctionAbstract_inNamespace + +#define arginfo_class_ReflectionClass_getModuleName arginfo_class_ReflectionFunctionAbstract_getName + #define arginfo_class_ReflectionClass_inNamespace arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionClass_getNamespaceName arginfo_class_ReflectionFunctionAbstract_getName @@ -843,6 +847,8 @@ ZEND_METHOD(ReflectionClass, isIterable); ZEND_METHOD(ReflectionClass, implementsInterface); ZEND_METHOD(ReflectionClass, getExtension); ZEND_METHOD(ReflectionClass, getExtensionName); +ZEND_METHOD(ReflectionClass, inModule); +ZEND_METHOD(ReflectionClass, getModuleName); ZEND_METHOD(ReflectionClass, inNamespace); ZEND_METHOD(ReflectionClass, getNamespaceName); ZEND_METHOD(ReflectionClass, getShortName); @@ -1135,6 +1141,8 @@ static const zend_function_entry class_ReflectionClass_methods[] = { ZEND_ME(ReflectionClass, implementsInterface, arginfo_class_ReflectionClass_implementsInterface, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getExtension, arginfo_class_ReflectionClass_getExtension, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getExtensionName, arginfo_class_ReflectionClass_getExtensionName, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, inModule, arginfo_class_ReflectionClass_inModule, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, getModuleName, arginfo_class_ReflectionClass_getModuleName, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, inNamespace, arginfo_class_ReflectionClass_inNamespace, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getNamespaceName, arginfo_class_ReflectionClass_getNamespaceName, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getShortName, arginfo_class_ReflectionClass_getShortName, ZEND_ACC_PUBLIC) diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 78315e9880b4f..25e558a0d30c4 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -14,6 +14,7 @@ +----------------------------------------------------------------------+ */ +#include "zend_string.h" #ifdef HAVE_CONFIG_H #include #endif @@ -35,6 +36,7 @@ #include "zend_exceptions.h" #include "zend_interfaces.h" #include "main/snprintf.h" +#include "zend_smart_str.h" ZEND_TLS zend_string *spl_autoload_extensions; ZEND_TLS HashTable *spl_autoload_functions; diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index a046ab50e1498..95cc52988dbaa 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -107,6 +107,7 @@ char *get_token_type_name(int token_type) case T_ENUM: return "T_ENUM"; case T_EXTENDS: return "T_EXTENDS"; case T_IMPLEMENTS: return "T_IMPLEMENTS"; + case T_MODULE: return "T_MODULE"; case T_NAMESPACE: return "T_NAMESPACE"; case T_LIST: return "T_LIST"; case T_ARRAY: return "T_ARRAY"; @@ -120,6 +121,7 @@ char *get_token_type_name(int token_type) case T_FUNC_C: return "T_FUNC_C"; case T_PROPERTY_C: return "T_PROPERTY_C"; case T_NS_C: return "T_NS_C"; + case T_MODULE_C: return "T_MODULE_C"; case T_ATTRIBUTE: return "T_ATTRIBUTE"; case T_PLUS_EQUAL: return "T_PLUS_EQUAL"; case T_MINUS_EQUAL: return "T_MINUS_EQUAL"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index 45f3c89f2de3a..fa2cb8575358e 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -412,6 +412,11 @@ * @cvalue T_IMPLEMENTS */ const T_IMPLEMENTS = UNKNOWN; +/** + * @var int + * @cvalue T_MODULE + */ +const T_MODULE = UNKNOWN; /** * @var int * @cvalue T_NAMESPACE @@ -477,6 +482,11 @@ * @cvalue T_NS_C */ const T_NS_C = UNKNOWN; +/** + * @var int + * @cvalue T_MODULE_C + */ +const T_MODULE_C = UNKNOWN; /** * @var int * @cvalue T_ATTRIBUTE diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h index 61f6ac1ec3659..7bf8f93dd358b 100644 --- a/ext/tokenizer/tokenizer_data_arginfo.h +++ b/ext/tokenizer/tokenizer_data_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: d917cab61a2b436a16d2227cdb438add45e42d69 */ + * Stub hash: 26c6bf1778c2fa25af1fa9e0425ff1e876103482 */ static void register_tokenizer_data_symbols(int module_number) { @@ -85,6 +85,7 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_ENUM", T_ENUM, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_EXTENDS", T_EXTENDS, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_IMPLEMENTS", T_IMPLEMENTS, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_MODULE", T_MODULE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_NAMESPACE", T_NAMESPACE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_LIST", T_LIST, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ARRAY", T_ARRAY, CONST_PERSISTENT); @@ -98,6 +99,7 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_FUNC_C", T_FUNC_C, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_PROPERTY_C", T_PROPERTY_C, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_NS_C", T_NS_C, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_MODULE_C", T_MODULE_C, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ATTRIBUTE", T_ATTRIBUTE, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_PLUS_EQUAL", T_PLUS_EQUAL, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_MINUS_EQUAL", T_MINUS_EQUAL, CONST_PERSISTENT);