From 2cd229f407064d62cd904be0c7acf254023356c3 Mon Sep 17 00:00:00 2001 From: Akihito Koriyama Date: Tue, 5 Nov 2024 02:39:46 +0900 Subject: [PATCH] Add method interception and improve error handling Added method interception capabilities including new interface and argument declarations for method interceptors. Also improved error handling by introducing detailed error messages and checking for memory allocation failures, ensuring robustness and easier debugging. --- config.m4 | 43 ++------ php_rayaop.h | 20 +++- rayaop.c | 192 +++++++++++++++++++++++++----------- tests/001-rayaop-basic.phpt | 45 ++++++--- 4 files changed, 188 insertions(+), 112 deletions(-) diff --git a/config.m4 b/config.m4 index aedefcd..e4b69bd 100644 --- a/config.m4 +++ b/config.m4 @@ -1,57 +1,26 @@ -dnl $Id$ dnl config.m4 for extension rayaop -dnl Include PECL configuration macro -dnl link https://github.com/php/pecl-tools/blob/master/autoconf/pecl.m4 -sinclude(./autoconf/pecl.m4) +PHP_ARG_ENABLE(rayaop, whether to enable rayaop, +[ --enable-rayaop Enable rayaop]) -dnl Include macro for detecting PHP executable -dnl link https://github.com/php/pecl-tools/blob/master/autoconf/php-executable.m4 -sinclude(./autoconf/php-executable.m4) - -dnl Initialize PECL extension -dnl link https://github.com/php/pecl-tools/blob/master/pecl.m4#L229 -PECL_INIT([rayaop]) - -dnl Add configuration option to enable the extension -dnl link https://www.gnu.org/software/autoconf/manual/autoconf-2.68/html_node/External-Shell-Variables.html -PHP_ARG_ENABLE(rayaop, whether to enable rayaop, [ --enable-rayaop Enable rayaop]) - -dnl Process if the extension is enabled if test "$PHP_RAYAOP" != "no"; then dnl Define whether the extension is enabled - dnl link https://www.gnu.org/software/autoconf/manual/autoconf-2.68/html_node/Defining-Variables.html AC_DEFINE(HAVE_RAYAOP, 1, [whether rayaop is enabled]) dnl Add new PHP extension - dnl link https://www.phpinternalsbook.com/build_system/build_system.html PHP_NEW_EXTENSION(rayaop, rayaop.c, $ext_shared) dnl Add Makefile fragment - dnl link https://www.phpinternalsbook.com/build_system/build_system.html#php-add-makefile-fragment PHP_ADD_MAKEFILE_FRAGMENT dnl Add instruction to install header files - dnl link https://www.phpinternalsbook.com/build_system/build_system.html#php-install-headers PHP_INSTALL_HEADERS([ext/rayaop], [php_rayaop.h]) + dnl Add quiet mode option PHP_ARG_ENABLE(rayaop-quiet, whether to suppress experimental notices, - [ --enable-rayaop-quiet Suppress experimental notices], no, yes) + [ --enable-rayaop-quiet Suppress experimental notices], no, yes) if test "$PHP_RAYAOP_QUIET" != "no"; then - AC_DEFINE(RAYAOP_QUIET, 1, [Whether to suppress experimental notices]) + AC_DEFINE(RAYAOP_QUIET, 1, [Whether to suppress experimental notices]) fi - - dnl Add AddressSanitizer flags - dnl Add the AddressSanitizer runtime library path - PHP_ADD_LIBRARY_WITH_PATH([clang_rt.asan_osx_dynamic], [/opt/homebrew/opt/llvm/lib/clang/14.0.0/lib/darwin], [RAYAOP_SHARED_LIBADD]) - AC_DEFINE(HAVE_ASAN, 1, [Define if you have AddressSanitizer]) - - dnl Add compiler and linker flags - CFLAGS="-g -O0 -fsanitize=address $CFLAGS" - LDFLAGS="-fsanitize=address $LDFLAGS" - - dnl Add include and library paths - PHP_ADD_INCLUDE([/opt/homebrew/opt/llvm/include]) - PHP_ADD_LIBPATH([/opt/homebrew/opt/llvm/lib]) -fi +fi \ No newline at end of file diff --git a/php_rayaop.h b/php_rayaop.h index 67ecb3f..e0ba748 100644 --- a/php_rayaop.h +++ b/php_rayaop.h @@ -25,7 +25,8 @@ #include "TSRM.h" #endif -/* Version and namespace definitions */ +/* Constants */ +#define MAX_EXECUTION_DEPTH 100 #define PHP_RAYAOP_VERSION "1.0.0" #define RAYAOP_NS "Ray\\Aop\\" @@ -35,6 +36,20 @@ #define RAYAOP_E_INVALID_HANDLER 3 #define RAYAOP_E_MAX_DEPTH_EXCEEDED 4 +/* Argument information declarations */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_method_intercept, 0, 0, 3) + ZEND_ARG_TYPE_INFO(0, class_name, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, method_name, IS_STRING, 0) + ZEND_ARG_OBJ_INFO(0, interceptor, Ray\\Aop\\MethodInterceptorInterface, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_method_intercept_init, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_enable_method_intercept, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0) +ZEND_END_ARG_INFO() + /* Debug mode configuration */ #ifdef RAYAOP_DEBUG #define PHP_RAYAOP_DEBUG_PRINT(fmt, ...) \ @@ -58,6 +73,9 @@ extern zend_module_entry rayaop_module_entry; #define phpext_rayaop_ptr &rayaop_module_entry +/* Interface class entry */ +extern zend_class_entry *ray_aop_method_interceptor_interface_ce; + /* Windows DLL export */ #ifdef PHP_WIN32 #define PHP_RAYAOP_API __declspec(dllexport) diff --git a/rayaop.c b/rayaop.c index 9fd825b..f148835 100644 --- a/rayaop.c +++ b/rayaop.c @@ -7,11 +7,32 @@ /* Module globals initialization */ ZEND_DECLARE_MODULE_GLOBALS(rayaop) -/* Original zend_execute_ex function pointer */ +/* Global variable declarations */ +zend_class_entry *ray_aop_method_interceptor_interface_ce; static void (*php_rayaop_original_execute_ex)(zend_execute_data *execute_data) = NULL; -/* Maximum execution depth to prevent infinite recursion */ -#define MAX_EXECUTION_DEPTH 100 +/* Argument information for the interceptor method */ +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ray_aop_method_interceptor_intercept, 0, 3, IS_MIXED, 0) + ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) + ZEND_ARG_TYPE_INFO(0, method, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, params, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +/* Interface methods */ +static zend_function_entry ray_aop_method_interceptor_interface_methods[] = { + PHP_ABSTRACT_ME(Ray_Aop_MethodInterceptorInterface, intercept, arginfo_ray_aop_method_interceptor_intercept) + PHP_FE_END +}; + +/* Module function declarations */ +static const zend_function_entry rayaop_functions[] = { + PHP_FE(method_intercept, arginfo_method_intercept) + PHP_FE(method_intercept_init, arginfo_method_intercept_init) + PHP_FE(enable_method_intercept, arginfo_enable_method_intercept) + PHP_FE_END +}; + + /* Module globals initializer */ static void php_rayaop_init_globals(zend_rayaop_globals *globals) { @@ -22,29 +43,23 @@ static void php_rayaop_init_globals(zend_rayaop_globals *globals) { globals->debug_level = 0; } -/* Error code handling */ -static const char *php_rayaop_get_error_message(int error_code) { +/* Error handling function */ +PHP_RAYAOP_API void php_rayaop_handle_error(int error_code, const char *message) { switch (error_code) { case RAYAOP_E_MEMORY_ALLOCATION: - return "Memory allocation failed"; + php_error_docref(NULL, E_ERROR, "Memory allocation failed: %s", message); + break; case RAYAOP_E_HASH_UPDATE: - return "Hash table update failed"; + php_error_docref(NULL, E_ERROR, "Hash table update failed: %s", message); + break; case RAYAOP_E_INVALID_HANDLER: - return "Invalid handler"; + php_error_docref(NULL, E_WARNING, "Invalid handler: %s", message); + break; case RAYAOP_E_MAX_DEPTH_EXCEEDED: - return "Maximum execution depth exceeded"; + php_error_docref(NULL, E_WARNING, "Maximum execution depth exceeded: %s", message); + break; default: - return "Unknown error"; - } -} - -/* Error handling function */ -PHP_RAYAOP_API void php_rayaop_handle_error(int error_code, const char *message) { - const char *error_type = php_rayaop_get_error_message(error_code); - if (message) { - php_error_docref(NULL, E_ERROR, "RayAOP Error (%s): %s", error_type, message); - } else { - php_error_docref(NULL, E_ERROR, "RayAOP Error: %s", error_type); + php_error_docref(NULL, E_ERROR, "RayAOP Error (%d): %s", error_code, message); } } @@ -82,7 +97,7 @@ PHP_RAYAOP_API bool php_rayaop_should_intercept(zend_execute_data *execute_data) } if (RAYAOP_G(execution_depth) >= MAX_EXECUTION_DEPTH) { - php_rayaop_handle_error(RAYAOP_E_MAX_DEPTH_EXCEEDED, NULL); + php_rayaop_handle_error(RAYAOP_E_MAX_DEPTH_EXCEEDED, "Maximum execution depth reached"); return false; } @@ -153,10 +168,15 @@ static bool execute_intercept_handler(zval *handler, zval *params, zval *retval) return success; } -/* Core interception execution function */ + +/* Fixed rayaop_execute_ex function */ static void rayaop_execute_ex(zend_execute_data *execute_data) { if (!php_rayaop_should_intercept(execute_data)) { - php_rayaop_original_execute_ex(execute_data); + if (php_rayaop_original_execute_ex) { + php_rayaop_original_execute_ex(execute_data); + } else { + zend_execute_ex(execute_data); + } return; } @@ -173,7 +193,9 @@ static void rayaop_execute_ex(zend_execute_data *execute_data) { if (Z_TYPE(info->handler) != IS_OBJECT) { php_rayaop_handle_error(RAYAOP_E_INVALID_HANDLER, "Invalid interceptor type"); - php_rayaop_original_execute_ex(execute_data); + if (php_rayaop_original_execute_ex) { + php_rayaop_original_execute_ex(execute_data); + } } else { zval retval; zval params[3]; @@ -183,7 +205,7 @@ static void rayaop_execute_ex(zend_execute_data *execute_data) { ZVAL_UNDEF(&retval); if (execute_intercept_handler(&info->handler, params, &retval)) { - if (!Z_ISUNDEF(retval)) { + if (!Z_ISUNDEF(retval) && execute_data->return_value) { ZVAL_COPY(execute_data->return_value, &retval); } zval_ptr_dtor(&retval); @@ -193,27 +215,14 @@ static void rayaop_execute_ex(zend_execute_data *execute_data) { RAYAOP_G(is_intercepting) = 0; } } else { - php_rayaop_original_execute_ex(execute_data); + if (php_rayaop_original_execute_ex) { + php_rayaop_original_execute_ex(execute_data); + } } efree(key); RAYAOP_G(execution_depth)--; } - -/* Method interceptor interface arguments */ -ZEND_BEGIN_ARG_INFO_EX(arginfo_method_intercept, 0, 0, 3) - ZEND_ARG_TYPE_INFO(0, class_name, IS_STRING, 0) - ZEND_ARG_TYPE_INFO(0, method_name, IS_STRING, 0) - ZEND_ARG_OBJ_INFO(0, interceptor, Ray\\Aop\\MethodInterceptorInterface, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO(arginfo_method_intercept_init, 0) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_enable_method_intercept, 0, 1, IS_VOID, 0) - ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0) -ZEND_END_ARG_INFO() - /* Implementation of method_intercept function */ PHP_FUNCTION(method_intercept) { char *class_name, *method_name; @@ -261,14 +270,19 @@ PHP_FUNCTION(method_intercept) { RETURN_TRUE; } -/* Implementation of method_intercept_init function */ +/* method_intercept_init function fix */ PHP_FUNCTION(method_intercept_init) { RAYAOP_G_LOCK(); if (RAYAOP_G(intercept_ht)) { zend_hash_clean(RAYAOP_G(intercept_ht)); } else { ALLOC_HASHTABLE(RAYAOP_G(intercept_ht)); - zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, php_rayaop_free_intercept_info, 0); + if (!RAYAOP_G(intercept_ht)) { + RAYAOP_G_UNLOCK(); + php_rayaop_handle_error(RAYAOP_E_MEMORY_ALLOCATION, "Failed to allocate intercept hash table"); + RETURN_FALSE; + } + zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, (dtor_func_t)php_rayaop_free_intercept_info, 0); } RAYAOP_G_UNLOCK(); RETURN_TRUE; @@ -302,20 +316,21 @@ PHP_MINIT_FUNCTION(rayaop) { php_rayaop_init_globals(&rayaop_globals); #endif + // Register the interface zend_class_entry ce; - INIT_CLASS_ENTRY(ce, "Ray\\Aop\\MethodInterceptorInterface", NULL); - zend_class_entry *interface_ce = zend_register_internal_interface(&ce); + INIT_NS_CLASS_ENTRY(ce, "Ray\\Aop", "MethodInterceptorInterface", ray_aop_method_interceptor_interface_methods); + ray_aop_method_interceptor_interface_ce = zend_register_internal_interface(&ce); + // Initialize other settings + RAYAOP_G(method_intercept_enabled) = 1; + RAYAOP_G(debug_level) = 0; php_rayaop_original_execute_ex = zend_execute_ex; - RAYAOP_G(method_intercept_enabled) = 0; - - REGISTER_LONG_CONSTANT("RAYAOP_DEBUG_LEVEL_NONE", 0, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("RAYAOP_DEBUG_LEVEL_BASIC", 1, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("RAYAOP_DEBUG_LEVEL_VERBOSE", 2, CONST_CS | CONST_PERSISTENT); + zend_execute_ex = rayaop_execute_ex; return SUCCESS; } + /* Module shutdown */ PHP_MSHUTDOWN_FUNCTION(rayaop) { if (php_rayaop_original_execute_ex) { @@ -324,12 +339,17 @@ PHP_MSHUTDOWN_FUNCTION(rayaop) { return SUCCESS; } -/* Request initialization */ +/* Request initialization function fix */ PHP_RINIT_FUNCTION(rayaop) { RAYAOP_G_LOCK(); if (!RAYAOP_G(intercept_ht)) { ALLOC_HASHTABLE(RAYAOP_G(intercept_ht)); - zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, php_rayaop_free_intercept_info, 0); + if (!RAYAOP_G(intercept_ht)) { + RAYAOP_G_UNLOCK(); + php_rayaop_handle_error(RAYAOP_E_MEMORY_ALLOCATION, "Failed to initialize intercept hash table"); + return FAILURE; + } + zend_hash_init(RAYAOP_G(intercept_ht), 8, NULL, (dtor_func_t)php_rayaop_free_intercept_info, 0); } RAYAOP_G(is_intercepting) = 0; RAYAOP_G(execution_depth) = 0; @@ -362,13 +382,67 @@ PHP_MINFO_FUNCTION(rayaop) { php_info_print_table_end(); } -/* Extension function entries */ -static const zend_function_entry rayaop_functions[] = { - PHP_FE(method_intercept, arginfo_method_intercept) - PHP_FE(method_intercept_init, arginfo_method_intercept_init) - PHP_FE(enable_method_intercept, arginfo_enable_method_intercept) - PHP_FE_END -}; +#ifdef RAYAOP_DEBUG +/* Debug functions */ +void php_rayaop_debug_print_zval(zval *value) { + if (!value) { + php_printf("NULL\n"); + return; + } + + switch (Z_TYPE_P(value)) { + case IS_NULL: + php_printf("NULL\n"); + break; + case IS_TRUE: + php_printf("bool(true)\n"); + break; + case IS_FALSE: + php_printf("bool(false)\n"); + break; + case IS_LONG: + php_printf("int(%ld)\n", Z_LVAL_P(value)); + break; + case IS_DOUBLE: + php_printf("float(%g)\n", Z_DVAL_P(value)); + break; + case IS_STRING: + php_printf("string(%d) \"%s\"\n", Z_STRLEN_P(value), Z_STRVAL_P(value)); + break; + case IS_ARRAY: + php_printf("array(%d) {...}\n", zend_hash_num_elements(Z_ARRVAL_P(value))); + break; + case IS_OBJECT: + php_printf("object(%s)#%d {...}\n", + Z_OBJCE_P(value)->name->val, Z_OBJ_HANDLE_P(value)); + break; + default: + php_printf("unknown type(%d)\n", Z_TYPE_P(value)); + } +} + +void php_rayaop_debug_dump_intercept_info(void) { + RAYAOP_G_LOCK(); + if (RAYAOP_G(intercept_ht)) { + php_printf("=== Intercept Information Dump ===\n"); + php_rayaop_intercept_info *info; + zend_string *key; + ZEND_HASH_FOREACH_STR_KEY_PTR(RAYAOP_G(intercept_ht), key, info) { + if (key && info) { + php_printf("Key: %s\n", ZSTR_VAL(key)); + php_printf(" Class: %s\n", ZSTR_VAL(info->class_name)); + php_printf(" Method: %s\n", ZSTR_VAL(info->method_name)); + php_printf(" Enabled: %d\n", info->is_enabled); + php_printf(" Handler type: %d\n", Z_TYPE(info->handler)); + } + } ZEND_HASH_FOREACH_END(); + php_printf("================================\n"); + } else { + php_printf("No intercept information available\n"); + } + RAYAOP_G_UNLOCK(); +} +#endif /* Module entry */ zend_module_entry rayaop_module_entry = { diff --git a/tests/001-rayaop-basic.phpt b/tests/001-rayaop-basic.phpt index 9985ce4..f99320a 100644 --- a/tests/001-rayaop-basic.phpt +++ b/tests/001-rayaop-basic.phpt @@ -1,32 +1,47 @@ --TEST-- RayAOP basic functionality +--SKIPIF-- + --FILE-- $method(...$params); } } -class TestInterceptor implements Ray\Aop\MethodInterceptorInterface { - public function intercept(object $object, string $method, array $params): mixed { - return "Intercepted: " . $object->$method(...$params); +class TestClass +{ + public function testMethod($param = '') + { + return "Original" . $param; } } +// Initialize the intercept table +var_dump(method_intercept_init()); + // Register the interceptor -$result = method_intercept(TestClass::class, 'testMethod', new TestInterceptor()); -enable_method_intercept(true); -var_dump($result); +var_dump(method_intercept('TestClass', 'testMethod', new TestInterceptor())); -// Call the intercepted method $test = new TestClass(); -$result = $test->testMethod("Hello"); -var_dump($result); +echo $test->testMethod(" method called") . "\n"; +// Disable method interception +enable_method_intercept(false); +echo $test->testMethod(" method called without interception") . "\n"; ?> ---EXPECTF-- +--EXPECT-- +bool(true) +bool(true) bool(true) -string(28) "Intercepted: Original: Hello" \ No newline at end of file +Intercepted: Original method called +Original method called without interception \ No newline at end of file